mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-02-03 11:59:48 +00:00
Compare commits
36 Commits
release/2.
...
f12786a0c6
| Author | SHA1 | Date | |
|---|---|---|---|
| f12786a0c6 | |||
| b6309caa32 | |||
| f3fef1daf5 | |||
| 0aaf096d73 | |||
| a1b6041ca8 | |||
| a36de5c4b9 | |||
| 1bcdce4e6e | |||
| 2983d7e61a | |||
| 43fb950a0a | |||
| a28589ec74 | |||
| dfab5f56b2 | |||
| 10798b9ba3 | |||
| 358e3dc4d2 | |||
| c13cd029bc | |||
| ae32cc8fbb | |||
| dff5b775a9 | |||
| db3f20546e | |||
| 53368468a4 | |||
| 228c2cce51 | |||
| f5aec4e5c1 | |||
| 9217c8c759 | |||
| 6392520e06 | |||
| c6306631e6 | |||
| 97b906c536 | |||
| 435375721d | |||
| 4c5caba6b7 | |||
| b83df9d2c5 | |||
| f2ea76aab9 | |||
| 471b1c1331 | |||
| bd770aac2f | |||
| 6abe35ef8c | |||
| fdc27a9ebf | |||
| b729096a25 | |||
| 390b9da29e | |||
| 256376df85 | |||
| 939a94d889 |
@@ -165,6 +165,11 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
|
|
||||||
# Blank line again and package imports
|
# Blank line again and package imports
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
# Multiline import example
|
||||||
|
from ahriman.core.database.operations import (
|
||||||
|
AuthOperations,
|
||||||
|
BuildOperations,
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines.
|
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines.
|
||||||
@@ -215,7 +220,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
127
docs/_static/architecture.dot
vendored
127
docs/_static/architecture.dot
vendored
@@ -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="#ef4306",fontcolor="#ffffff",label="ahriman\.\ncore\.\nutils"];
|
ahriman_core_utils [fillcolor="#db3d05",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="#c73705",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nrepository_paths"];
|
ahriman_models_repository_paths [fillcolor="#d13a05",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,57 +952,56 @@ 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="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_application_ahriman [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_application_application_application_packages [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_application_application_packages [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_add [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_add [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_daemon [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_daemon [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_rebuild [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_rebuild [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_setup [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_setup [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_statistics [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_statistics [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_status [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_status [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_status_update [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_status_update [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_update [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_update [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_handlers_users [fillcolor="#ef4306",minlen="3"];
|
ahriman_core_utils -> ahriman_application_handlers_users [fillcolor="#db3d05",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_application_lock [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_application_lock [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_core_alpm_pacman [fillcolor="#ef4306",minlen="2",weight="2"];
|
ahriman_core_utils -> ahriman_core_alpm_pacman [fillcolor="#db3d05",minlen="2",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_core_alpm_repo [fillcolor="#ef4306",minlen="2",weight="2"];
|
ahriman_core_utils -> ahriman_core_alpm_repo [fillcolor="#db3d05",minlen="2",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_core_auth_pam [fillcolor="#ef4306",minlen="2",weight="2"];
|
ahriman_core_utils -> ahriman_core_auth_pam [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_package_archive [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_sources [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_build_tools_task [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_aur_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_repository_stats_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_formatters_update_printer [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_gitremote_remote_pull [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_email [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_report_jinja_template [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_executor [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_repository_package_info [fillcolor="#db3d05",minlen="2",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_core_sign_gpg [fillcolor="#ef4306",minlen="2",weight="2"];
|
ahriman_core_utils -> ahriman_core_sign_gpg [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_support_pkgbuild_pkgbuild_generator [fillcolor="#db3d05",minlen="2",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_core_tree [fillcolor="#ef4306",weight="2"];
|
ahriman_core_utils -> ahriman_core_tree [fillcolor="#db3d05",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_core_upload_github [fillcolor="#ef4306",minlen="2",weight="2"];
|
ahriman_core_utils -> ahriman_core_upload_github [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_rsync [fillcolor="#db3d05",minlen="2",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_core_upload_s3 [fillcolor="#ef4306",minlen="2",weight="2"];
|
ahriman_core_utils -> ahriman_core_upload_s3 [fillcolor="#db3d05",minlen="2",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_core_util [fillcolor="#ef4306",weight="2"];
|
ahriman_core_utils -> ahriman_core_util [fillcolor="#db3d05",weight="2"];
|
||||||
ahriman_core_utils -> ahriman_models_aur_package [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_aur_package [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_build_status [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_build_status [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_changes [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_changes [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_counters [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_counters [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_dependencies [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_dependencies [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_event [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_event [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_filesystem_package [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_filesystem_package [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_internal_status [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_internal_status [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_package [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_package [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_package_description [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_package_description [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_package_source [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_package_source [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_pkgbuild_patch [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_pkgbuild_patch [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_remote_source [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_remote_source [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_repository_paths [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_repository_stats [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_repository_stats [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_models_worker [fillcolor="#db3d05",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_worker [fillcolor="#ef4306",minlen="2"];
|
ahriman_core_utils -> ahriman_web_views_api_swagger [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="#db3d05",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"];
|
||||||
@@ -1137,6 +1136,7 @@ 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,16 +1475,17 @@ 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="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_application_handlers_handler [fillcolor="#d13a05",minlen="3"];
|
||||||
ahriman_models_repository_paths -> ahriman_application_handlers_setup [fillcolor="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_application_handlers_setup [fillcolor="#d13a05",minlen="3"];
|
||||||
ahriman_models_repository_paths -> ahriman_application_handlers_tree_migrate [fillcolor="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_application_handlers_tree_migrate [fillcolor="#d13a05",minlen="3"];
|
||||||
ahriman_models_repository_paths -> ahriman_core_alpm_repo [fillcolor="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_core_alpm_repo [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_sources [fillcolor="#d13a05",minlen="3"];
|
||||||
ahriman_models_repository_paths -> ahriman_core_build_tools_task [fillcolor="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_core_build_tools_task [fillcolor="#d13a05",minlen="3"];
|
||||||
ahriman_models_repository_paths -> ahriman_core_configuration_configuration [fillcolor="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_core_configuration_configuration [fillcolor="#d13a05",minlen="3"];
|
||||||
ahriman_models_repository_paths -> ahriman_core_repository_repository_properties [fillcolor="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_core_repository_repository_properties [fillcolor="#d13a05",minlen="3"];
|
||||||
ahriman_models_repository_paths -> ahriman_models_package_source [fillcolor="#c73705",weight="2"];
|
ahriman_models_repository_paths -> ahriman_core_utils [fillcolor="#d13a05",minlen="2"];
|
||||||
ahriman_models_repository_paths -> ahriman_web_views_v1_service_upload [fillcolor="#c73705",minlen="3"];
|
ahriman_models_repository_paths -> ahriman_models_package_source [fillcolor="#d13a05",weight="2"];
|
||||||
|
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"];
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ 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
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
|
|||||||
29
docs/ahriman.core.archive.rst
Normal file
29
docs/ahriman.core.archive.rst
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
ahriman.core.archive package
|
||||||
|
============================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
ahriman.core.archive.archive\_tree module
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.archive.archive_tree
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.core.archive.archive\_trigger module
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.archive.archive_trigger
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.archive
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
@@ -132,6 +132,14 @@ ahriman.core.database.migrations.m015\_logs\_process\_id module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.core.database.migrations.m016\_archive module
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.database.migrations.m016_archive
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|||||||
29
docs/ahriman.core.housekeeping.rst
Normal file
29
docs/ahriman.core.housekeeping.rst
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
ahriman.core.housekeeping package
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
ahriman.core.housekeeping.archive\_rotation\_trigger module
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.housekeeping.archive_rotation_trigger
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.core.housekeeping.logs\_rotation\_trigger module
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.housekeeping.logs_rotation_trigger
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.housekeeping
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
@@ -8,6 +8,7 @@ Subpackages
|
|||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
ahriman.core.alpm
|
ahriman.core.alpm
|
||||||
|
ahriman.core.archive
|
||||||
ahriman.core.auth
|
ahriman.core.auth
|
||||||
ahriman.core.build_tools
|
ahriman.core.build_tools
|
||||||
ahriman.core.configuration
|
ahriman.core.configuration
|
||||||
@@ -15,6 +16,7 @@ Subpackages
|
|||||||
ahriman.core.distributed
|
ahriman.core.distributed
|
||||||
ahriman.core.formatters
|
ahriman.core.formatters
|
||||||
ahriman.core.gitremote
|
ahriman.core.gitremote
|
||||||
|
ahriman.core.housekeeping
|
||||||
ahriman.core.http
|
ahriman.core.http
|
||||||
ahriman.core.log
|
ahriman.core.log
|
||||||
ahriman.core.report
|
ahriman.core.report
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ 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
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
@@ -140,6 +148,14 @@ 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
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ 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
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ This package contains everything required for the most of application actions an
|
|||||||
* ``ahriman.core.distributed`` package with triggers and helpers for distributed build system.
|
* ``ahriman.core.distributed`` package with triggers and helpers for distributed build system.
|
||||||
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
||||||
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
||||||
|
* ``ahriman.core.housekeeping`` package provides few triggers for removing old data.
|
||||||
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
||||||
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
||||||
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ will try to read value from ``SECRET`` environment variable. In case if the requ
|
|||||||
|
|
||||||
will eventually lead ``key`` option in section ``section1`` to be set to the value of ``HOME`` environment variable (if available).
|
will eventually lead ``key`` option in section ``section1`` to be set to the value of ``HOME`` environment variable (if available).
|
||||||
|
|
||||||
|
Moreover, configuration can be read from environment variables directly by following the same naming convention, e.g. in the example above, one can have environment variable named ``section1:key`` (e.g. ``section1:key=$HOME``) and it will be substituted to the configuration with the highest priority.
|
||||||
|
|
||||||
There is also additional subcommand which will allow to validate configuration and print found errors. In order to do so, run ``service-config-validate`` subcommand, e.g.:
|
There is also additional subcommand which will allow to validate configuration and print found errors. In order to do so, run ``service-config-validate`` subcommand, e.g.:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
@@ -81,7 +83,6 @@ Base configuration settings.
|
|||||||
* ``apply_migrations`` - perform database migrations on the application start, boolean, optional, default ``yes``. Useful if you are using git version. Note, however, that this option must be changed only if you know what to do and going to handle migrations manually.
|
* ``apply_migrations`` - perform database migrations on the application start, boolean, optional, default ``yes``. Useful if you are using git version. Note, however, that this option must be changed only if you know what to do and going to handle migrations manually.
|
||||||
* ``database`` - path to the application SQLite database, string, required.
|
* ``database`` - path to the application SQLite database, string, required.
|
||||||
* ``include`` - path to directory with configuration files overrides, string, optional. Files will be read in alphabetical order.
|
* ``include`` - path to directory with configuration files overrides, string, optional. Files will be read in alphabetical order.
|
||||||
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, optional ,default ``0``. Logs will be cleared at the end of each process.
|
|
||||||
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
||||||
|
|
||||||
``alpm:*`` groups
|
``alpm:*`` groups
|
||||||
@@ -96,6 +97,13 @@ libalpm and AUR related configuration. Group name can refer to architecture, e.g
|
|||||||
* ``sync_files_database`` - download files database from mirror, boolean, required.
|
* ``sync_files_database`` - download files database from mirror, boolean, required.
|
||||||
* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands). If set to ``no``, databases must be synchronized manually.
|
* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands). If set to ``no``, databases must be synchronized manually.
|
||||||
|
|
||||||
|
``archive`` group
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Describes settings for packages archives management extensions.
|
||||||
|
|
||||||
|
* ``keep_built_packages`` - keep this amount of built packages with different versions, integer, required. ``0`` (or negative number) will effectively disable archives removal.
|
||||||
|
|
||||||
``auth`` group
|
``auth`` group
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@@ -168,6 +176,7 @@ 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.
|
||||||
@@ -181,7 +190,7 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
|
|||||||
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
||||||
|
|
||||||
``keyring`` group
|
``keyring`` group
|
||||||
--------------------
|
-----------------
|
||||||
|
|
||||||
Keyring package generator plugin.
|
Keyring package generator plugin.
|
||||||
|
|
||||||
@@ -199,6 +208,13 @@ Keyring generator plugin
|
|||||||
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
||||||
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
||||||
|
|
||||||
|
``housekeeping`` group
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
This section describes settings for the ``ahriman.core.housekeeping.LogsRotationTrigger`` plugin.
|
||||||
|
|
||||||
|
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, optional ,default ``0``. Logs will be cleared at the end of each process.
|
||||||
|
|
||||||
``mirrorlist`` group
|
``mirrorlist`` group
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|||||||
@@ -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.4
|
pkgver=2.19.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
@@ -40,6 +40,7 @@ package_ahriman-core() {
|
|||||||
'rsync: sync by using rsync')
|
'rsync: sync by using rsync')
|
||||||
install="$pkgbase.install"
|
install="$pkgbase.install"
|
||||||
backup=('etc/ahriman.ini'
|
backup=('etc/ahriman.ini'
|
||||||
|
'etc/ahriman.ini.d/00-housekeeping.ini'
|
||||||
'etc/ahriman.ini.d/logging.ini')
|
'etc/ahriman.ini.d/logging.ini')
|
||||||
|
|
||||||
cd "$pkgbase-$pkgver"
|
cd "$pkgbase-$pkgver"
|
||||||
@@ -49,6 +50,7 @@ package_ahriman-core() {
|
|||||||
|
|
||||||
# keep usr/share configs as reference and copy them to /etc
|
# keep usr/share configs as reference and copy them to /etc
|
||||||
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini"
|
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini"
|
||||||
|
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/00-housekeeping.ini" "$pkgdir/etc/ahriman.ini.d/00-housekeeping.ini"
|
||||||
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini"
|
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini"
|
||||||
|
|
||||||
install -Dm644 "$srcdir/$pkgbase.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgbase.conf"
|
install -Dm644 "$srcdir/$pkgbase.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgbase.conf"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ logging = ahriman.ini.d/logging.ini
|
|||||||
;apply_migrations = yes
|
;apply_migrations = yes
|
||||||
; Path to the application SQLite database.
|
; Path to the application SQLite database.
|
||||||
database = ${repository:root}/ahriman.db
|
database = ${repository:root}/ahriman.db
|
||||||
; Keep last build logs for each package
|
|
||||||
keep_last_logs = 5
|
|
||||||
|
|
||||||
[alpm]
|
[alpm]
|
||||||
; Path to pacman system database cache.
|
; Path to pacman system database cache.
|
||||||
@@ -45,9 +43,13 @@ triggers[] = ahriman.core.gitremote.RemotePullTrigger
|
|||||||
triggers[] = ahriman.core.report.ReportTrigger
|
triggers[] = ahriman.core.report.ReportTrigger
|
||||||
triggers[] = ahriman.core.upload.UploadTrigger
|
triggers[] = ahriman.core.upload.UploadTrigger
|
||||||
triggers[] = ahriman.core.gitremote.RemotePushTrigger
|
triggers[] = ahriman.core.gitremote.RemotePushTrigger
|
||||||
|
triggers[] = ahriman.core.housekeeping.LogsRotationTrigger
|
||||||
|
triggers[] = ahriman.core.housekeeping.ArchiveRotationTrigger
|
||||||
; List of well-known triggers. Used only for configuration purposes.
|
; List of well-known triggers. Used only for configuration purposes.
|
||||||
triggers_known[] = ahriman.core.gitremote.RemotePullTrigger
|
triggers_known[] = ahriman.core.gitremote.RemotePullTrigger
|
||||||
triggers_known[] = ahriman.core.gitremote.RemotePushTrigger
|
triggers_known[] = ahriman.core.gitremote.RemotePushTrigger
|
||||||
|
triggers_known[] = ahriman.core.housekeeping.ArchiveRotationTrigger
|
||||||
|
triggers_known[] = ahriman.core.housekeeping.LogsRotationTrigger
|
||||||
triggers_known[] = ahriman.core.report.ReportTrigger
|
triggers_known[] = ahriman.core.report.ReportTrigger
|
||||||
triggers_known[] = ahriman.core.upload.UploadTrigger
|
triggers_known[] = ahriman.core.upload.UploadTrigger
|
||||||
; Maximal age in seconds of the VCS packages before their version will be updated with its remote source.
|
; Maximal age in seconds of the VCS packages before their version will be updated with its remote source.
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[archive]
|
||||||
|
; Keep amount of last built packages in archive. 0 means keep all packages
|
||||||
|
keep_built_packages = 1
|
||||||
|
|
||||||
|
[logs-rotation]
|
||||||
|
; Keep last build logs for each package
|
||||||
|
keep_last_logs = 5
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
[build]
|
[build]
|
||||||
; List of well-known triggers. Used only for configuration purposes.
|
; List of well-known triggers. Used only for configuration purposes.
|
||||||
|
triggers_known[] = ahriman.core.archive.ArchiveTrigger
|
||||||
triggers_known[] = ahriman.core.distributed.WorkerLoaderTrigger
|
triggers_known[] = ahriman.core.distributed.WorkerLoaderTrigger
|
||||||
triggers_known[] = ahriman.core.distributed.WorkerTrigger
|
triggers_known[] = ahriman.core.distributed.WorkerTrigger
|
||||||
triggers_known[] = ahriman.core.support.KeyringTrigger
|
triggers_known[] = ahriman.core.support.KeyringTrigger
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ 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.
|
||||||
|
|||||||
@@ -80,10 +80,28 @@
|
|||||||
<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"
|
||||||
@@ -102,8 +120,8 @@
|
|||||||
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"
|
data-toolbar="#toolbar"
|
||||||
data-toolbar="#toolbar">
|
data-unique-id="id">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
<th data-checkbox="true"></th>
|
<th data-checkbox="true"></th>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
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}`)) return; // check if there are duplicates
|
if (alertPlaceholder.querySelector(`#alert-${id}`)) {
|
||||||
|
return; // check if there are duplicates
|
||||||
|
}
|
||||||
|
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.id = `alert-${id}`;
|
wrapper.id = `alert-${id}`;
|
||||||
|
|||||||
@@ -51,6 +51,87 @@
|
|||||||
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",
|
||||||
|
|||||||
@@ -148,8 +148,19 @@
|
|||||||
|
|
||||||
packageAddInput.addEventListener("keyup", _ => {
|
packageAddInput.addEventListener("keyup", _ => {
|
||||||
clearTimeout(packageAddInput.requestTimeout);
|
clearTimeout(packageAddInput.requestTimeout);
|
||||||
packageAddInput.requestTimeout = setTimeout(_ => {
|
|
||||||
|
// do not update datalist if search string didn't change yet
|
||||||
const value = packageAddInput.value;
|
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(_ => {
|
||||||
|
|
||||||
if (value.length >= 3) {
|
if (value.length >= 3) {
|
||||||
makeRequest(
|
makeRequest(
|
||||||
|
|||||||
@@ -80,8 +80,7 @@
|
|||||||
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>
|
||||||
@@ -98,10 +97,24 @@
|
|||||||
<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()" data-bs-dismiss="modal"><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()"><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>
|
||||||
@@ -140,6 +153,10 @@
|
|||||||
|
|
||||||
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) {
|
||||||
@@ -148,6 +165,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -291,6 +315,69 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
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`,
|
||||||
{
|
{
|
||||||
@@ -319,15 +406,19 @@
|
|||||||
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 = _ => {
|
||||||
const logs = data
|
// check if we are at the bottom of the code block
|
||||||
.filter(log_record => log_record.version === version.version && log_record.process_id === version.process_id)
|
const isScrolledToBottom = packageInfoLogsInput.scrollTop + packageInfoLogsInput.clientHeight >= packageInfoLogsInput.scrollHeight;
|
||||||
.map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`);
|
packageInfoLogsInput.textContent = link.dataset.logs;
|
||||||
|
|
||||||
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");
|
||||||
@@ -403,12 +494,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packageInfoRemove() {
|
function packageInfoRemove() {
|
||||||
const packageBase = packageInfoModal.package;
|
const packageBase = packageInfoModal.dataset.package;
|
||||||
packagesRemove([packageBase]);
|
packagesRemove([packageBase]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function packageInfoUpdate() {
|
function packageInfoUpdate() {
|
||||||
const packageBase = packageInfoModal.package;
|
const packageBase = packageInfoModal.dataset.package;
|
||||||
packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
|
packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,10 +507,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.package = packageBase;
|
packageInfoModal.dataset.package = packageBase;
|
||||||
} else {
|
} else {
|
||||||
// read package base from the current window attribute
|
// read package base from the current window attribute
|
||||||
packageBase = packageInfoModal.package;
|
packageBase = packageInfoModal.dataset.package;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFailure = error => {
|
const onFailure = error => {
|
||||||
@@ -438,10 +529,27 @@
|
|||||||
|
|
||||||
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: {},
|
||||||
@@ -468,6 +576,11 @@
|
|||||||
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>
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
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,
|
||||||
@@ -55,6 +59,41 @@
|
|||||||
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`;
|
||||||
@@ -88,46 +127,7 @@
|
|||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
table.bootstrapTable("showLoading");
|
table.bootstrapTable("showLoading");
|
||||||
|
const onFailure = error => {
|
||||||
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/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)) {
|
if ((error.status === 401) || (error.status === 403)) {
|
||||||
// authorization error
|
// authorization error
|
||||||
const text = "In order to see statuses you must login first.";
|
const text = "In order to see statuses you must login first.";
|
||||||
@@ -139,79 +139,10 @@
|
|||||||
const message = details => `Could not load list of packages: ${details}`;
|
const message = details => `Could not load list of packages: ${details}`;
|
||||||
showFailure("Load failure", message, error);
|
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) {
|
packagesLoad(onFailure);
|
||||||
dashboardPackagesCountChart.config.data = {
|
statusLoad();
|
||||||
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() {
|
||||||
@@ -230,7 +161,24 @@
|
|||||||
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 = {
|
||||||
@@ -245,18 +193,16 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", _ => {
|
table.bootstrapTable({
|
||||||
if (packageRemoveButton) {
|
onCheck: onCheckFunction,
|
||||||
packageRemoveButton.disabled = !table.bootstrapTable("getSelections").length;
|
onCheckAll: onCheckFunction,
|
||||||
}
|
onClickRow: (data, row, cell) => {
|
||||||
});
|
|
||||||
table.on("click-row.bs.table", (self, data, row, cell) => {
|
|
||||||
if (0 === cell || "base" === cell) {
|
if (0 === cell || "base" === cell) {
|
||||||
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
|
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
|
||||||
table.bootstrapTable(method, {field: "id", values: [data.id]});
|
table.bootstrapTable(method, {field: "id", values: [data.id]});
|
||||||
} else showPackageInfo(data.id);
|
} else showPackageInfo(data.id);
|
||||||
});
|
},
|
||||||
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: [
|
||||||
@@ -286,8 +232,16 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
onUncheck: onCheckFunction,
|
||||||
|
onUncheckAll: onCheckFunction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
restoreAutoReloadSettings(tableAutoReloadButton, tableAutoReloadInput);
|
||||||
|
|
||||||
selectRepository();
|
selectRepository();
|
||||||
|
{% if autorefresh_intervals %}
|
||||||
|
toggleTableAutoReload();
|
||||||
|
{% endif %}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ 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>
|
||||||
@@ -128,7 +127,8 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
ready(_ => {
|
||||||
table.on("created-controls.bs.table", _ => {
|
table.bootstrapTable({
|
||||||
|
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: [
|
||||||
@@ -158,6 +158,7 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
<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.30.0/tableExport.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/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.3/dist/js/bootstrap.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-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/bootstrap-table.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/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/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/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/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/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.10.0/build/highlight.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/npm/chart.js@4.4.4/dist/chart.umd.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>
|
<script>
|
||||||
async function copyToClipboard(text, button) {
|
async function copyToClipboard(text, button) {
|
||||||
@@ -58,6 +59,20 @@
|
|||||||
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"];
|
||||||
@@ -106,6 +121,12 @@
|
|||||||
.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);
|
||||||
@@ -114,6 +135,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, "&")
|
.replace(/&/g, "&")
|
||||||
@@ -133,7 +159,86 @@
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
Date.prototype.toISOStringShort = function() {
|
function toggleActiveElement(selector, dataType, value) {
|
||||||
|
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())}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<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@5.3.7/dist/css/bootstrap.min.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-icons@1.13.1/font/bootstrap-icons.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/bootstrap-table@1.24.1/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.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/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/bootswatch@5.3.3/dist/cosmo/bootstrap.min.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/gh/highlightjs/cdn-release@11.10.0/build/styles/github.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">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.pre-scrollable {
|
.pre-scrollable {
|
||||||
|
|||||||
@@ -674,7 +674,6 @@ _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
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
.TH AHRIMAN "1" "2026\-02\-02" "ahriman 2.19.4" "ArcH linux ReposItory MANager"
|
.TH AHRIMAN "1" "2025\-06\-29" "ahriman 2.19.0" "ArcH linux ReposItory MANager"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ahriman \- ArcH linux ReposItory MANager
|
ahriman \- ArcH linux ReposItory MANager
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B [0m[1;35mahriman[0m
|
.B ahriman
|
||||||
[[32m-h[0m] [[32m-a [33mARCHITECTURE[0m] [[32m-c [33mCONFIGURATION[0m] [[36m--force[0m] [[32m-l [33mLOCK[0m] [[36m--log-handler [33m{console,syslog,journald}[0m] [[32m-q[0m] [[36m--report | --no-report[0m] [[32m-r [33mREPOSITORY[0m] [[36m--unsafe[0m] [[32m-V[0m] [[36m--wait-timeout [33mWAIT_TIMEOUT[0m] [32m{add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web} ...[0m
|
[-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
|
||||||
[1;34musage: [0m[1;35mahriman aur\-search[0m [[32m\-h[0m] [[32m\-e[0m] [[36m\-\-info | \-\-no\-info[0m]
|
usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info]
|
||||||
[[36m\-\-sort\-by [33m{description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}[0m]
|
[\-\-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}]
|
||||||
[32msearch [search ...][0m
|
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
|
||||||
[1;34musage: [0m[1;35mahriman help[0m [[32m\-h[0m] [32m[subcommand][0m
|
usage: ahriman help [\-h] [subcommand]
|
||||||
|
|
||||||
show help message for application or command and exit
|
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
|
||||||
[1;34musage: [0m[1;35mahriman help\-commands\-unsafe[0m [[32m\-h[0m] [32m[subcommand ...][0m
|
usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...]
|
||||||
|
|
||||||
list unsafe commands as defined in default args
|
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
|
||||||
[1;34musage: [0m[1;35mahriman help\-updates[0m [[32m\-h[0m] [[32m\-e[0m]
|
usage: ahriman help\-updates [\-h] [\-e]
|
||||||
|
|
||||||
request AUR for current version and compare with current service version
|
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
|
||||||
[1;34musage: [0m[1;35mahriman help\-version[0m [[32m\-h[0m]
|
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
|
||||||
[1;34musage: [0m[1;35mahriman package\-add[0m [[32m\-h[0m] [[36m\-\-changes | \-\-no\-changes[0m] [[36m\-\-dependencies | \-\-no\-dependencies[0m] [[32m\-e[0m]
|
usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e]
|
||||||
[[36m\-\-increment | \-\-no\-increment[0m] [[32m\-n[0m] [[32m\-y[0m]
|
[\-\-increment | \-\-no\-increment] [\-n] [\-y]
|
||||||
[[32m\-s [33m{auto,archive,aur,directory,local,remote,repository}[0m] [[32m\-u [33mUSERNAME[0m] [[32m\-v [33mVARIABLE[0m]
|
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE]
|
||||||
[32mpackage [package ...][0m
|
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
|
||||||
[1;34musage: [0m[1;35mahriman package\-changes[0m [[32m\-h[0m] [[32m\-e[0m] [32mpackage[0m
|
usage: ahriman package\-changes [\-h] [\-e] package
|
||||||
|
|
||||||
retrieve package changes stored in database
|
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
|
||||||
[1;34musage: [0m[1;35mahriman package\-changes\-remove[0m [[32m\-h[0m] [32mpackage[0m
|
usage: ahriman package\-changes\-remove [\-h] package
|
||||||
|
|
||||||
remove the package changes stored remotely
|
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
|
||||||
[1;34musage: [0m[1;35mahriman package\-copy[0m [[32m\-h[0m] [[32m\-e[0m] [[36m\-\-remove[0m] [32msource[0m [32mpackage [package ...][0m
|
usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...]
|
||||||
|
|
||||||
copy package and its metadata from another repository
|
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
|
||||||
[1;34musage: [0m[1;35mahriman package\-remove[0m [[32m\-h[0m] [32mpackage [package ...][0m
|
usage: ahriman package\-remove [\-h] package [package ...]
|
||||||
|
|
||||||
remove package from the repository
|
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
|
||||||
[1;34musage: [0m[1;35mahriman package\-status[0m [[32m\-h[0m] [[36m\-\-ahriman[0m] [[32m\-e[0m] [[36m\-\-info | \-\-no\-info[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m]
|
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}]
|
||||||
[32m[package ...][0m
|
[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
|
||||||
[1;34musage: [0m[1;35mahriman package\-status\-remove[0m [[32m\-h[0m] [32mpackage [package ...][0m
|
usage: ahriman package\-status\-remove [\-h] package [package ...]
|
||||||
|
|
||||||
remove the package from the status page
|
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
|
||||||
[1;34musage: [0m[1;35mahriman package\-status\-update[0m [[32m\-h[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m] [32m[package ...][0m
|
usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...]
|
||||||
|
|
||||||
update package status on the status page
|
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
|
||||||
[1;34musage: [0m[1;35mahriman patch\-add[0m [[32m\-h[0m] [32mpackage[0m [32mvariable[0m [32m[patch][0m
|
usage: ahriman patch\-add [\-h] package variable [patch]
|
||||||
|
|
||||||
create or update patched PKGBUILD function or variable
|
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
|
||||||
[1;34musage: [0m[1;35mahriman patch\-list[0m [[32m\-h[0m] [[32m\-e[0m] [[32m\-v [33mVARIABLE[0m] [32mpackage[0m
|
usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package
|
||||||
|
|
||||||
list available patches for the package
|
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
|
||||||
[1;34musage: [0m[1;35mahriman patch\-remove[0m [[32m\-h[0m] [[32m\-v [33mVARIABLE[0m] [32mpackage[0m
|
usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package
|
||||||
|
|
||||||
remove patches for the package
|
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
|
||||||
[1;34musage: [0m[1;35mahriman patch\-set\-add[0m [[32m\-h[0m] [[32m\-t [33mTRACK[0m] [32mpackage[0m
|
usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package
|
||||||
|
|
||||||
create or update source patches
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-backup[0m [[32m\-h[0m] [32mpath[0m
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-check[0m [[32m\-h[0m] [[36m\-\-changes | \-\-no\-changes[0m] [[36m\-\-check\-files | \-\-no\-check\-files[0m] [[32m\-e[0m] [[36m\-\-vcs | \-\-no\-vcs[0m]
|
usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs]
|
||||||
[[32m\-y[0m]
|
[\-y]
|
||||||
[32m[package ...][0m
|
[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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-create\-keyring[0m [[32m\-h[0m]
|
usage: ahriman repo\-create\-keyring [\-h]
|
||||||
|
|
||||||
create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-create\-mirrorlist[0m [[32m\-h[0m]
|
usage: ahriman repo\-create\-mirrorlist [\-h]
|
||||||
|
|
||||||
create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-daemon[0m [[32m\-h[0m] [[32m\-i [33mINTERVAL[0m] [[36m\-\-aur | \-\-no\-aur[0m] [[36m\-\-changes | \-\-no\-changes[0m]
|
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
|
||||||
[[36m\-\-check\-files | \-\-no\-check\-files[0m] [[36m\-\-dependencies | \-\-no\-dependencies[0m] [[36m\-\-dry\-run[0m]
|
[\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run]
|
||||||
[[36m\-\-increment | \-\-no\-increment[0m] [[36m\-\-local | \-\-no\-local[0m] [[36m\-\-manual | \-\-no\-manual[0m]
|
[\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual]
|
||||||
[[36m\-\-partitions | \-\-no\-partitions[0m] [[32m\-u [33mUSERNAME[0m] [[36m\-\-vcs | \-\-no\-vcs[0m] [[32m\-y[0m]
|
[\-\-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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-rebuild[0m [[32m\-h[0m] [[36m\-\-depends\-on [33mDEPENDS_ON[0m] [[36m\-\-dry\-run[0m] [[36m\-\-from\-database[0m] [[36m\-\-increment | \-\-no\-increment[0m]
|
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment]
|
||||||
[[32m\-e[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m] [[32m\-u [33mUSERNAME[0m]
|
[\-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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-remove\-unknown[0m [[32m\-h[0m] [[36m\-\-dry\-run[0m]
|
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
|
||||||
|
|
||||||
remove packages which are missing in AUR and do not have local PKGBUILDs
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-report[0m [[32m\-h[0m]
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-restore[0m [[32m\-h[0m] [[32m\-o [33mOUTPUT[0m] [32mpath[0m
|
usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path
|
||||||
|
|
||||||
restore settings and database
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-sign[0m [[32m\-h[0m] [32m[package ...][0m
|
usage: ahriman repo\-sign [\-h] [package ...]
|
||||||
|
|
||||||
(re\-)sign packages and repository database according to current settings
|
(re\-)sign packages and repository database according to current settings
|
||||||
|
|
||||||
@@ -658,10 +658,10 @@ root path of the extracted files
|
|||||||
sign only specified packages
|
sign only specified packages
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR
|
.SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR
|
||||||
[1;34musage: [0m[1;35mahriman repo\-statistics[0m [[32m\-h[0m] [[36m\-\-chart [33mCHART[0m]
|
usage: ahriman repo\-statistics [\-h] [\-\-chart CHART]
|
||||||
[[32m\-e [33m{package\-outdated,package\-removed,package\-update\-failed,package\-updated}[0m]
|
[\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}]
|
||||||
[[36m\-\-from\-date [33mFROM_DATE[0m] [[36m\-\-limit [33mLIMIT[0m] [[36m\-\-offset [33mOFFSET[0m] [[36m\-\-to\-date [33mTO_DATE[0m]
|
[\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE]
|
||||||
[32m[package][0m
|
[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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-status\-update[0m [[32m\-h[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m]
|
usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}]
|
||||||
|
|
||||||
update repository status on the status page
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-sync[0m [[32m\-h[0m]
|
usage: ahriman repo\-sync [\-h]
|
||||||
|
|
||||||
sync repository files to remote server according to current settings
|
sync repository files to remote server according to current settings
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman repo\-tree'\/\fR
|
.SH COMMAND \fI\,'ahriman repo\-tree'\/\fR
|
||||||
[1;34musage: [0m[1;35mahriman repo\-tree[0m [[32m\-h[0m] [[32m\-p [33mPARTITIONS[0m]
|
usage: ahriman repo\-tree [\-h] [\-p PARTITIONS]
|
||||||
|
|
||||||
dump repository tree based on packages dependencies
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-triggers[0m [[32m\-h[0m] [32m[trigger ...][0m
|
usage: ahriman repo\-triggers [\-h] [trigger ...]
|
||||||
|
|
||||||
run triggers on empty build result as configured by settings
|
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
|
||||||
[1;34musage: [0m[1;35mahriman repo\-update[0m [[32m\-h[0m] [[36m\-\-aur | \-\-no\-aur[0m] [[36m\-\-changes | \-\-no\-changes[0m] [[36m\-\-check\-files | \-\-no\-check\-files[0m]
|
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files]
|
||||||
[[36m\-\-dependencies | \-\-no\-dependencies[0m] [[36m\-\-dry\-run[0m] [[32m\-e[0m] [[36m\-\-increment | \-\-no\-increment[0m]
|
[\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment]
|
||||||
[[36m\-\-local | \-\-no\-local[0m] [[36m\-\-manual | \-\-no\-manual[0m] [[32m\-u [33mUSERNAME[0m] [[36m\-\-vcs | \-\-no\-vcs[0m] [[32m\-y[0m]
|
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
|
||||||
[32m[package ...][0m
|
[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
|
||||||
[1;34musage: [0m[1;35mahriman service\-clean[0m [[32m\-h[0m] [[36m\-\-cache | \-\-no\-cache[0m] [[36m\-\-chroot | \-\-no\-chroot[0m] [[36m\-\-manual | \-\-no\-manual[0m]
|
usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual]
|
||||||
[[36m\-\-packages | \-\-no\-packages[0m] [[36m\-\-pacman | \-\-no\-pacman[0m]
|
[\-\-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
|
||||||
[1;34musage: [0m[1;35mahriman service\-config[0m [[32m\-h[0m] [[36m\-\-info | \-\-no\-info[0m] [[36m\-\-secure | \-\-no\-secure[0m] [32m[section][0m [32m[key][0m
|
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
|
||||||
|
|
||||||
dump configuration for the specified architecture
|
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
|
||||||
[1;34musage: [0m[1;35mahriman service\-config\-validate[0m [[32m\-h[0m] [[32m\-e[0m]
|
usage: ahriman service\-config\-validate [\-h] [\-e]
|
||||||
|
|
||||||
validate configuration and print found errors
|
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
|
||||||
[1;34musage: [0m[1;35mahriman service\-key\-import[0m [[32m\-h[0m] [[36m\-\-key\-server [33mKEY_SERVER[0m] [32mkey[0m
|
usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key
|
||||||
|
|
||||||
import PGP key from public sources to the repository user
|
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
|
||||||
[1;34musage: [0m[1;35mahriman service\-repositories[0m [[32m\-h[0m] [[36m\-\-id\-only | \-\-no\-id\-only[0m]
|
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
|
||||||
|
|
||||||
list all available repositories
|
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
|
||||||
[1;34musage: [0m[1;35mahriman service\-run[0m [[32m\-h[0m] [32mcommand [command ...][0m
|
usage: ahriman service\-run [\-h] command [command ...]
|
||||||
|
|
||||||
run multiple commands on success run of the previous command
|
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
|
||||||
[1;34musage: [0m[1;35mahriman service\-setup[0m [[32m\-h[0m] [[36m\-\-build\-as\-user [33mBUILD_AS_USER[0m] [[36m\-\-from\-configuration [33mFROM_CONFIGURATION[0m]
|
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
|
||||||
[[36m\-\-generate\-salt | \-\-no\-generate\-salt[0m] [[36m\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs[0m]
|
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
|
||||||
[[36m\-\-mirror [33mMIRROR[0m] [[36m\-\-multilib | \-\-no\-multilib[0m] [36m\-\-packager [33mPACKAGER[0m [[36m\-\-server [33mSERVER[0m]
|
[\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER]
|
||||||
[[36m\-\-sign\-key [33mSIGN_KEY[0m] [[36m\-\-sign\-target [33m{disabled,packages,repository}[0m] [[36m\-\-web\-port [33mWEB_PORT[0m]
|
[\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
|
||||||
[[36m\-\-web\-unix\-socket [33mWEB_UNIX_SOCKET[0m]
|
[\-\-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
|
||||||
[1;34musage: [0m[1;35mahriman service\-shell[0m [[32m\-h[0m] [[32m\-o [33mOUTPUT[0m] [32m[code][0m
|
usage: ahriman service\-shell [\-h] [\-o OUTPUT] [code]
|
||||||
|
|
||||||
drop into python shell
|
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
|
||||||
[1;34musage: [0m[1;35mahriman service\-tree\-migrate[0m [[32m\-h[0m]
|
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
|
||||||
[1;34musage: [0m[1;35mahriman user\-add[0m [[32m\-h[0m] [[36m\-\-key [33mKEY[0m] [[36m\-\-packager [33mPACKAGER[0m] [[32m\-p [33mPASSWORD[0m] [[32m\-R [33m{unauthorized,read,reporter,full}[0m]
|
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}]
|
||||||
[32musername[0m
|
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
|
||||||
[1;34musage: [0m[1;35mahriman user\-list[0m [[32m\-h[0m] [[32m\-e[0m] [[32m\-R [33m{unauthorized,read,reporter,full}[0m] [32m[username][0m
|
usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username]
|
||||||
|
|
||||||
list users from the user mapping and their roles
|
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
|
||||||
[1;34musage: [0m[1;35mahriman user\-remove[0m [[32m\-h[0m] [32musername[0m
|
usage: ahriman user\-remove [\-h] username
|
||||||
|
|
||||||
remove user from the user mapping and update the configuration
|
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
|
||||||
[1;34musage: [0m[1;35mahriman web[0m [[32m\-h[0m]
|
usage: ahriman web [\-h]
|
||||||
|
|
||||||
start web server
|
start web server
|
||||||
|
|
||||||
|
|||||||
@@ -99,9 +99,6 @@ _shtab_ahriman_options=(
|
|||||||
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
|
"--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:"
|
||||||
@@ -116,9 +113,6 @@ _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)]"
|
||||||
@@ -127,9 +121,6 @@ _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:"
|
||||||
@@ -140,9 +131,6 @@ _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:"
|
||||||
@@ -152,9 +140,6 @@ _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:"
|
||||||
@@ -163,17 +148,11 @@ _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)]"
|
||||||
@@ -182,9 +161,6 @@ _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:"
|
||||||
@@ -202,40 +178,25 @@ _shtab_ahriman_daemon_options=(
|
|||||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
"*"{-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:"
|
||||||
@@ -252,18 +213,12 @@ _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:"
|
||||||
@@ -278,26 +233,17 @@ _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)]"
|
||||||
@@ -306,17 +252,11 @@ _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)]"
|
||||||
@@ -326,26 +266,17 @@ _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:"
|
||||||
@@ -360,9 +291,6 @@ _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:"
|
||||||
@@ -370,9 +298,6 @@ _shtab_ahriman_patch_add_options=(
|
|||||||
":path to file which contains function or variable value. If not set, the value will be read from stdin (default\: None):"
|
":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)]"
|
||||||
@@ -380,27 +305,18 @@ _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:"
|
||||||
@@ -412,33 +328,21 @@ _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:"
|
||||||
@@ -449,9 +353,6 @@ _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:"
|
||||||
@@ -461,9 +362,6 @@ _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:"
|
||||||
@@ -472,31 +370,19 @@ _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:"
|
||||||
@@ -514,9 +400,6 @@ _shtab_ahriman_repo_daemon_options=(
|
|||||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
"*"{-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:"
|
||||||
@@ -533,9 +416,6 @@ _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:"
|
||||||
@@ -547,33 +427,21 @@ _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:"
|
||||||
@@ -590,17 +458,11 @@ _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:"
|
||||||
@@ -612,40 +474,25 @@ _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:"
|
||||||
@@ -663,24 +510,15 @@ _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)]"
|
||||||
@@ -689,9 +527,6 @@ _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:"
|
||||||
@@ -701,9 +536,6 @@ _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:"
|
||||||
@@ -712,42 +544,27 @@ _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:"
|
||||||
@@ -764,25 +581,16 @@ _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:"
|
||||||
@@ -799,26 +607,17 @@ _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)]"
|
||||||
@@ -828,25 +627,16 @@ _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:"
|
||||||
@@ -864,9 +654,6 @@ _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:"
|
||||||
@@ -876,9 +663,6 @@ _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)]"
|
||||||
@@ -886,42 +670,26 @@ _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='(-)*' default='*::: :->ahriman'
|
local context state line curcontext="$curcontext" one_or_more='(-)*' remainder='(*)'
|
||||||
|
|
||||||
# Add default positional/remainder specs only if none exist, and only once per session
|
if ((${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501
|
||||||
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')
|
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman')
|
||||||
fi
|
fi
|
||||||
_shtab_ahriman_defaults_added=1
|
|
||||||
fi
|
|
||||||
_arguments -C -s $_shtab_ahriman_options
|
_arguments -C -s $_shtab_ahriman_options
|
||||||
|
|
||||||
case $state in
|
case $state in
|
||||||
|
|||||||
@@ -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.4"
|
__version__ = "2.19.0"
|
||||||
|
|||||||
70
src/ahriman/application/handlers/reload.py
Normal file
70
src/ahriman/application/handlers/reload.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#
|
||||||
|
# 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]
|
||||||
@@ -28,6 +28,7 @@ from ahriman.core.alpm.remote import AUR, Official
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import OptionError
|
from ahriman.core.exceptions import OptionError
|
||||||
from ahriman.core.formatters import AurPrinter
|
from ahriman.core.formatters import AurPrinter
|
||||||
|
from ahriman.core.types import Comparable
|
||||||
from ahriman.models.aur_package import AURPackage
|
from ahriman.models.aur_package import AURPackage
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
|
||||||
@@ -115,7 +116,7 @@ class Search(Handler):
|
|||||||
raise OptionError(sort_by)
|
raise OptionError(sort_by)
|
||||||
# always sort by package name at the last
|
# always sort by package name at the last
|
||||||
# well technically it is not a string, but we can deal with it
|
# well technically it is not a string, but we can deal with it
|
||||||
comparator: Callable[[AURPackage], tuple[str, str]] =\
|
comparator: Callable[[AURPackage], Comparable] = \
|
||||||
lambda package: (getattr(package, sort_by), package.name)
|
lambda package: (getattr(package, sort_by), package.name)
|
||||||
return sorted(packages, key=comparator)
|
return sorted(packages, key=comparator)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from ahriman.application.application import Application
|
|||||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.formatters import PackagePrinter, StatusPrinter
|
from ahriman.core.formatters import PackagePrinter, StatusPrinter
|
||||||
|
from ahriman.core.types import Comparable
|
||||||
from ahriman.core.utils import enum_values
|
from ahriman.core.utils import enum_values
|
||||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
@@ -64,8 +65,8 @@ class Status(Handler):
|
|||||||
|
|
||||||
Status.check_status(args.exit_code, packages)
|
Status.check_status(args.exit_code, packages)
|
||||||
|
|
||||||
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda item: item[0].base
|
comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda item: item[0].base
|
||||||
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\
|
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] = \
|
||||||
lambda item: args.status is None or item[1].status == args.status
|
lambda item: args.status is None or item[1].status == args.status
|
||||||
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
|
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
|
||||||
PackagePrinter(package, package_status)(verbose=args.info)
|
PackagePrinter(package, package_status)(verbose=args.info)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import argparse
|
|||||||
|
|
||||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.utils import walk
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
from ahriman.models.repository_paths import RepositoryPaths
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ class TreeMigrate(Handler):
|
|||||||
target_tree.tree_create()
|
target_tree.tree_create()
|
||||||
# perform migration
|
# perform migration
|
||||||
TreeMigrate.tree_move(current_tree, target_tree)
|
TreeMigrate.tree_move(current_tree, target_tree)
|
||||||
|
TreeMigrate.fix_symlinks(target_tree)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
@@ -66,6 +68,22 @@ class TreeMigrate(Handler):
|
|||||||
parser.set_defaults(lock=None, quiet=True, report=False)
|
parser.set_defaults(lock=None, quiet=True, report=False)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fix_symlinks(paths: RepositoryPaths) -> None:
|
||||||
|
"""
|
||||||
|
fix packages archives symlinks
|
||||||
|
|
||||||
|
Args:
|
||||||
|
paths(RepositoryPaths): new repository paths
|
||||||
|
"""
|
||||||
|
archives = {path.name: path for path in walk(paths.archive)}
|
||||||
|
for symlink in walk(paths.repository):
|
||||||
|
if symlink.exists(): # no need to check for symlinks as we have just walked through the tree
|
||||||
|
continue
|
||||||
|
if (source_archive := archives.get(symlink.name)) is not None:
|
||||||
|
symlink.unlink()
|
||||||
|
symlink.symlink_to(source_archive.relative_to(symlink.parent, walk_up=True))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
|
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import argparse
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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) -> Iterator[tuple[str, str]]:
|
def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]:
|
||||||
"""
|
"""
|
||||||
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) -> Iterator[str]:
|
def dependencies_by_key(key: str) -> Generator[str, None, None]:
|
||||||
# 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:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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) -> Iterator[str]:
|
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]:
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -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.root, unsafe=self.unsafe)
|
check_user(self.paths, unsafe=self.unsafe)
|
||||||
self.paths.tree_create()
|
self.paths.tree_create()
|
||||||
|
|
||||||
def check_version(self) -> None:
|
def check_version(self) -> None:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import itertools
|
|||||||
import shutil
|
import shutil
|
||||||
import tarfile
|
import tarfile
|
||||||
|
|
||||||
from collections.abc import Iterable, Iterator
|
from collections.abc import Generator, Iterable
|
||||||
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]) -> Iterator[tuple[str, set[str]]]:
|
def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Generator[tuple[str, set[str]], None, None]:
|
||||||
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) -> Iterator[Package]:
|
def package(self, package_name: str) -> Generator[Package, None, None]:
|
||||||
"""
|
"""
|
||||||
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) -> Iterator[Package]:
|
def provided_by(self, package_name: str) -> Generator[Package, None, None]:
|
||||||
"""
|
"""
|
||||||
search through databases and emit packages which provides the ``package_name``
|
search through databases and emit packages which provides the ``package_name``
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import itertools
|
|||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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() -> Iterator[str]:
|
def extract() -> Generator[str, None, None]:
|
||||||
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) -> Iterator[PkgbuildPatch]:
|
def _parse_token(self, token: str) -> Generator[PkgbuildPatch, None, None]:
|
||||||
"""
|
"""
|
||||||
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) -> Iterator[PkgbuildPatch]:
|
def parse(self) -> Generator[PkgbuildPatch, None, None]:
|
||||||
"""
|
"""
|
||||||
parse source stream and yield parsed entries
|
parse source stream and yield parsed entries
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,15 @@ 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
|
||||||
|
|||||||
@@ -31,20 +31,21 @@ class Repo(LazyLogging):
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
name(str): repository name
|
name(str): repository name
|
||||||
paths(RepositoryPaths): repository paths instance
|
root(Path): repository root
|
||||||
sign_args(list[str]): additional args which have to be used to sign repository archive
|
sign_args(list[str]): additional args which have to be used to sign repository archive
|
||||||
uid(int): uid of the repository owner user
|
uid(int): uid of the repository owner user
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str]) -> None:
|
def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str], root: Path | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
name(str): repository name
|
name(str): repository name
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
sign_args(list[str]): additional args which have to be used to sign repository archive
|
sign_args(list[str]): additional args which have to be used to sign repository archive
|
||||||
|
root(Path | None, optional): repository root. If none set, the default will be used (Default value = None)
|
||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
self.paths = paths
|
self.root = root or paths.repository
|
||||||
self.uid, _ = paths.root_owner
|
self.uid, _ = paths.root_owner
|
||||||
self.sign_args = sign_args
|
self.sign_args = sign_args
|
||||||
|
|
||||||
@@ -56,45 +57,56 @@ class Repo(LazyLogging):
|
|||||||
Returns:
|
Returns:
|
||||||
Path: path to repository database
|
Path: path to repository database
|
||||||
"""
|
"""
|
||||||
return self.paths.repository / f"{self.name}.db.tar.gz"
|
return self.root / f"{self.name}.db.tar.gz"
|
||||||
|
|
||||||
def add(self, path: Path) -> None:
|
def add(self, path: Path, *, remove: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
add new package to repository
|
add new package to repository
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path(Path): path to archive to add
|
path(Path): path to archive to add
|
||||||
|
remove(bool, optional): whether to remove old packages or not (Default value = True)
|
||||||
"""
|
"""
|
||||||
|
command = ["repo-add", *self.sign_args]
|
||||||
|
if remove:
|
||||||
|
command.extend(["--remove"])
|
||||||
|
command.extend([str(self.repo_path), str(path)])
|
||||||
|
|
||||||
|
# add to repository
|
||||||
check_output(
|
check_output(
|
||||||
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path),
|
*command,
|
||||||
exception=BuildError.from_process(path.name),
|
exception=BuildError.from_process(path.name),
|
||||||
cwd=self.paths.repository,
|
cwd=self.root,
|
||||||
logger=self.logger,
|
logger=self.logger,
|
||||||
user=self.uid)
|
user=self.uid,
|
||||||
|
)
|
||||||
|
|
||||||
def init(self) -> None:
|
def init(self) -> None:
|
||||||
"""
|
"""
|
||||||
create empty repository database. It just calls add with empty arguments
|
create empty repository database. It just calls add with empty arguments
|
||||||
"""
|
"""
|
||||||
check_output("repo-add", *self.sign_args, str(self.repo_path),
|
check_output("repo-add", *self.sign_args, str(self.repo_path),
|
||||||
cwd=self.paths.repository, logger=self.logger, user=self.uid)
|
cwd=self.root, logger=self.logger, user=self.uid)
|
||||||
|
|
||||||
def remove(self, package: str, filename: Path) -> None:
|
def remove(self, package_name: str | None, filename: Path) -> None:
|
||||||
"""
|
"""
|
||||||
remove package from repository
|
remove package from repository
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package(str): package name to remove
|
package_name(str | None): package name to remove. If none set, it will be guessed from filename
|
||||||
filename(Path): package filename to remove
|
filename(Path): package filename to remove
|
||||||
"""
|
"""
|
||||||
|
package_name = package_name or filename.name.rsplit("-", maxsplit=3)[0]
|
||||||
|
|
||||||
# remove package and signature (if any) from filesystem
|
# remove package and signature (if any) from filesystem
|
||||||
for full_path in self.paths.repository.glob(f"{filename}*"):
|
for full_path in self.root.glob(f"**/{filename.name}*"):
|
||||||
full_path.unlink()
|
full_path.unlink()
|
||||||
|
|
||||||
# remove package from registry
|
# remove package from registry
|
||||||
check_output(
|
check_output(
|
||||||
"repo-remove", *self.sign_args, str(self.repo_path), package,
|
"repo-remove", *self.sign_args, str(self.repo_path), package_name,
|
||||||
exception=BuildError.from_process(package),
|
exception=BuildError.from_process(package_name),
|
||||||
cwd=self.paths.repository,
|
cwd=self.root,
|
||||||
logger=self.logger,
|
logger=self.logger,
|
||||||
user=self.uid)
|
user=self.uid,
|
||||||
|
)
|
||||||
|
|||||||
20
src/ahriman/core/archive/__init__.py
Normal file
20
src/ahriman/core/archive/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#
|
||||||
|
# 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.core.archive.archive_trigger import ArchiveTrigger
|
||||||
130
src/ahriman/core/archive/archive_tree.py
Normal file
130
src/ahriman/core/archive/archive_tree.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#
|
||||||
|
# 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 datetime
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ahriman.core.alpm.repo import Repo
|
||||||
|
from ahriman.core.log import LazyLogging
|
||||||
|
from ahriman.core.utils import utcnow, walk
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
|
|
||||||
|
class ArchiveTree(LazyLogging):
|
||||||
|
"""
|
||||||
|
wrapper around archive tree
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
paths(RepositoryPaths): repository paths instance
|
||||||
|
repository_id(RepositoryId): repository unique identifier
|
||||||
|
sign_args(list[str]): additional args which have to be used to sign repository archive
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, repository_path: RepositoryPaths, sign_args: list[str]) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
repository_path(RepositoryPaths): repository paths instance
|
||||||
|
sign_args(list[str]): additional args which have to be used to sign repository archive
|
||||||
|
"""
|
||||||
|
self.paths = repository_path
|
||||||
|
self.repository_id = repository_path.repository_id
|
||||||
|
self.sign_args = sign_args
|
||||||
|
|
||||||
|
def repository_for(self, date: datetime.date | None = None) -> Path:
|
||||||
|
"""
|
||||||
|
get full path to repository at the specified date
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date(datetime.date | None, optional): date to generate path. If none supplied then today will be used
|
||||||
|
(Default value = None)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: path to the repository root
|
||||||
|
"""
|
||||||
|
date = date or utcnow().date()
|
||||||
|
return (
|
||||||
|
self.paths.archive
|
||||||
|
/ "repos"
|
||||||
|
/ date.strftime("%Y")
|
||||||
|
/ date.strftime("%m")
|
||||||
|
/ date.strftime("%d")
|
||||||
|
/ self.repository_id.name
|
||||||
|
/ self.repository_id.architecture
|
||||||
|
)
|
||||||
|
|
||||||
|
def symlinks_create(self, packages: list[Package]) -> None:
|
||||||
|
"""
|
||||||
|
create symlinks for the specified packages in today's repository
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packages(list[Package]): list of packages to be updated
|
||||||
|
"""
|
||||||
|
root = self.repository_for()
|
||||||
|
repo = Repo(self.repository_id.name, self.paths, self.sign_args, root)
|
||||||
|
|
||||||
|
for package in packages:
|
||||||
|
archive = self.paths.archive_for(package.base)
|
||||||
|
|
||||||
|
for package_name, single in package.packages.items():
|
||||||
|
if single.filename is None:
|
||||||
|
self.logger.warning("received empty package filename for %s", package_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
has_file = False
|
||||||
|
for file in archive.glob(f"{single.filename}*"):
|
||||||
|
symlink = root / file.name
|
||||||
|
if symlink.exists():
|
||||||
|
continue # symlink is already created, skip processing
|
||||||
|
has_file = True
|
||||||
|
symlink.symlink_to(file.relative_to(symlink.parent, walk_up=True))
|
||||||
|
|
||||||
|
if has_file:
|
||||||
|
repo.add(root / single.filename)
|
||||||
|
|
||||||
|
def symlinks_fix(self) -> None:
|
||||||
|
"""
|
||||||
|
remove broken symlinks across repositories for all dates
|
||||||
|
"""
|
||||||
|
for path in walk(self.paths.archive / "repos"):
|
||||||
|
root = path.parent
|
||||||
|
*_, name, architecture = root.parts
|
||||||
|
if self.repository_id.name != name or self.repository_id.architecture != architecture:
|
||||||
|
continue # we only process same name repositories
|
||||||
|
|
||||||
|
if not path.is_symlink():
|
||||||
|
continue # find symlinks only
|
||||||
|
if path.exists():
|
||||||
|
continue # filter out not broken symlinks
|
||||||
|
|
||||||
|
Repo(self.repository_id.name, self.paths, self.sign_args, root).remove(None, path)
|
||||||
|
|
||||||
|
def tree_create(self) -> None:
|
||||||
|
"""
|
||||||
|
create repository tree for current repository
|
||||||
|
"""
|
||||||
|
root = self.repository_for()
|
||||||
|
if root.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
with self.paths.preserve_owner(self.paths.archive):
|
||||||
|
root.mkdir(0o755, parents=True)
|
||||||
|
# init empty repository here
|
||||||
|
Repo(self.repository_id.name, self.paths, self.sign_args, root).init()
|
||||||
69
src/ahriman/core/archive/archive_trigger.py
Normal file
69
src/ahriman/core/archive/archive_trigger.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#
|
||||||
|
# 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.core.archive.archive_tree import ArchiveTree
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
from ahriman.core.triggers import Trigger
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
|
class ArchiveTrigger(Trigger):
|
||||||
|
"""
|
||||||
|
archive repository extension
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
paths(RepositoryPaths): repository paths instance
|
||||||
|
tree(ArchiveTree): archive tree wrapper
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
repository_id(RepositoryId): repository unique identifier
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
"""
|
||||||
|
Trigger.__init__(self, repository_id, configuration)
|
||||||
|
|
||||||
|
self.paths = configuration.repository_paths
|
||||||
|
self.tree = ArchiveTree(self.paths, GPG(configuration).repository_sign_args)
|
||||||
|
|
||||||
|
def on_result(self, result: Result, packages: list[Package]) -> None:
|
||||||
|
"""
|
||||||
|
run trigger
|
||||||
|
|
||||||
|
Args:
|
||||||
|
result(Result): build result
|
||||||
|
packages(list[Package]): list of all available packages
|
||||||
|
"""
|
||||||
|
self.tree.symlinks_create(packages)
|
||||||
|
|
||||||
|
def on_start(self) -> None:
|
||||||
|
"""
|
||||||
|
trigger action which will be called at the start of the application
|
||||||
|
"""
|
||||||
|
self.tree.tree_create()
|
||||||
|
|
||||||
|
def on_stop(self) -> None:
|
||||||
|
"""
|
||||||
|
trigger action which will be called before the stop of the application
|
||||||
|
"""
|
||||||
|
self.tree.symlinks_fix()
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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() -> Iterator[str]:
|
def configuration_flags() -> Generator[str, None, None]:
|
||||||
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}\""
|
||||||
|
|||||||
@@ -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 Iterator
|
from collections.abc import Generator
|
||||||
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() -> Iterator[Path]:
|
def files() -> Generator[Path, None, None]:
|
||||||
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
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
# 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 os
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -84,9 +86,10 @@ 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: [self._convert_path(element) for element in shlex.split(value)],
|
"pathlist": lambda value: list(map(self._convert_path, shlex.split(value))),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -187,6 +190,7 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
"""
|
"""
|
||||||
configuration = cls()
|
configuration = cls()
|
||||||
configuration.load(path)
|
configuration.load(path)
|
||||||
|
configuration.load_environment()
|
||||||
configuration.merge_sections(repository_id)
|
configuration.merge_sections(repository_id)
|
||||||
return configuration
|
return configuration
|
||||||
|
|
||||||
@@ -261,6 +265,8 @@ 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]
|
||||||
@@ -309,6 +315,16 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
self.read(self.path)
|
self.read(self.path)
|
||||||
self.load_includes() # load includes
|
self.load_includes() # load includes
|
||||||
|
|
||||||
|
def load_environment(self) -> None:
|
||||||
|
"""
|
||||||
|
load environment variables into configuration
|
||||||
|
"""
|
||||||
|
for name, value in os.environ.items():
|
||||||
|
if ":" not in name:
|
||||||
|
continue
|
||||||
|
section, key = name.rsplit(":", maxsplit=1)
|
||||||
|
self.set_option(section, key, value)
|
||||||
|
|
||||||
def load_includes(self, path: Path | None = None) -> None:
|
def load_includes(self, path: Path | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
load configuration includes from specified path
|
load configuration includes from specified path
|
||||||
@@ -377,11 +393,16 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
"""
|
"""
|
||||||
reload configuration if possible or raise exception otherwise
|
reload configuration if possible or raise exception otherwise
|
||||||
"""
|
"""
|
||||||
|
# get current properties and validate input
|
||||||
path, repository_id = self.check_loaded()
|
path, repository_id = self.check_loaded()
|
||||||
for section in self.sections(): # clear current content
|
|
||||||
|
# clear current content
|
||||||
|
for section in self.sections():
|
||||||
self.remove_section(section)
|
self.remove_section(section)
|
||||||
self.load(path)
|
|
||||||
self.merge_sections(repository_id)
|
# create another instance and copy values from there
|
||||||
|
instance = self.from_path(path, repository_id)
|
||||||
|
self.copy_from(instance)
|
||||||
|
|
||||||
def set_option(self, section: str, option: str, value: str) -> None:
|
def set_option(self, section: str, option: str, value: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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() as current_value):
|
case [current_value] | str(current_value):
|
||||||
value = f"{current_value} {value}"
|
value = f"{current_value} {value}"
|
||||||
case None:
|
case None:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -45,11 +45,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
|||||||
"path_exists": True,
|
"path_exists": True,
|
||||||
"path_type": "dir",
|
"path_type": "dir",
|
||||||
},
|
},
|
||||||
"keep_last_logs": {
|
|
||||||
"type": "integer",
|
|
||||||
"coerce": "integer",
|
|
||||||
"min": 0,
|
|
||||||
},
|
|
||||||
"logging": {
|
"logging": {
|
||||||
"type": "path",
|
"type": "path",
|
||||||
"coerce": "absolute_path",
|
"coerce": "absolute_path",
|
||||||
@@ -328,6 +323,15 @@ 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",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import configparser
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from collections.abc import Iterator, Mapping, MutableMapping
|
from collections.abc import Generator, 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]) -> Iterator[tuple[str, str]]:
|
defaults: Mapping[str, str]) -> Generator[tuple[str, str], None, None]:
|
||||||
"""
|
"""
|
||||||
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() -> Iterator[tuple[str | None, str]]:
|
def identifiers() -> Generator[tuple[str | None, str], None, None]:
|
||||||
# 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):
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from collections.abc import Iterator, Mapping
|
from collections.abc import Generator, 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]) -> Iterator[tuple[str, str]]:
|
def generator(variables: dict[str, str]) -> Generator[tuple[str, str], None, None]:
|
||||||
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):
|
||||||
|
|||||||
84
src/ahriman/core/database/migrations/m016_archive.py
Normal file
84
src/ahriman/core/database/migrations/m016_archive.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#
|
||||||
|
# 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 dataclasses import replace
|
||||||
|
from sqlite3 import Connection
|
||||||
|
|
||||||
|
from ahriman.application.handlers.handler import Handler
|
||||||
|
from ahriman.core.alpm.pacman import Pacman
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||||
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["migrate_data"]
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_data(connection: Connection, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
perform data migration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
connection(Connection): database connection
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
"""
|
||||||
|
del connection
|
||||||
|
|
||||||
|
config_path, _ = configuration.check_loaded()
|
||||||
|
args = argparse.Namespace(configuration=config_path, architecture=None, repository=None, repository_id=None)
|
||||||
|
|
||||||
|
for repository_id in Handler.repositories_extract(args):
|
||||||
|
paths = replace(configuration.repository_paths, repository_id=repository_id)
|
||||||
|
pacman = Pacman(repository_id, configuration, refresh_database=PacmanSynchronization.Disabled)
|
||||||
|
|
||||||
|
# create archive directory if required
|
||||||
|
if not paths.archive.is_dir():
|
||||||
|
with paths.preserve_owner(paths.archive):
|
||||||
|
paths.archive.mkdir(mode=0o755, parents=True)
|
||||||
|
|
||||||
|
move_packages(paths, pacman)
|
||||||
|
|
||||||
|
|
||||||
|
def move_packages(repository_paths: RepositoryPaths, pacman: Pacman) -> None:
|
||||||
|
"""
|
||||||
|
move packages from repository to archive and create symbolic links
|
||||||
|
|
||||||
|
Args:
|
||||||
|
repository_paths(RepositoryPaths): repository paths instance
|
||||||
|
pacman(Pacman): alpm wrapper instance
|
||||||
|
"""
|
||||||
|
for source in repository_paths.repository.iterdir():
|
||||||
|
if not source.is_file(follow_symlinks=False):
|
||||||
|
continue # skip symbolic links if any
|
||||||
|
|
||||||
|
filename = source.name
|
||||||
|
if filename.startswith(".") or ".pkg." not in filename:
|
||||||
|
# we don't use package_like method here, because it also filters out signatures
|
||||||
|
continue
|
||||||
|
package = Package.from_archive(source, pacman)
|
||||||
|
|
||||||
|
# move package to the archive directory
|
||||||
|
target = repository_paths.archive_for(package.base) / filename
|
||||||
|
source.rename(target)
|
||||||
|
|
||||||
|
# create symlink to the archive
|
||||||
|
source.symlink_to(target.relative_to(source.parent, walk_up=True))
|
||||||
@@ -29,13 +29,15 @@ class LogsOperations(Operations):
|
|||||||
logs operations
|
logs operations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0,
|
def logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
||||||
repository_id: RepositoryId | None = None) -> list[LogRecord]:
|
limit: int = -1, offset: int = 0, 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)
|
||||||
@@ -52,12 +54,17 @@ 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 and repository = :repository
|
where package_base = :package_base
|
||||||
|
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,
|
||||||
|
|||||||
@@ -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 Iterable, Iterator
|
from collections.abc import Generator, Iterable
|
||||||
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) -> Iterator[tuple[Package, BuildStatus]]:
|
def run(connection: Connection) -> Generator[tuple[Package, BuildStatus], None, None]:
|
||||||
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)
|
||||||
|
|||||||
@@ -25,8 +25,16 @@ from typing import Self
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database.migrations import Migrations
|
from ahriman.core.database.migrations import Migrations
|
||||||
from ahriman.core.database.operations import AuthOperations, BuildOperations, ChangesOperations, \
|
from ahriman.core.database.operations import (
|
||||||
DependenciesOperations, EventOperations, LogsOperations, PackageOperations, PatchOperations
|
AuthOperations,
|
||||||
|
BuildOperations,
|
||||||
|
ChangesOperations,
|
||||||
|
DependenciesOperations,
|
||||||
|
EventOperations,
|
||||||
|
LogsOperations,
|
||||||
|
PackageOperations,
|
||||||
|
PatchOperations,
|
||||||
|
)
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 Iterator
|
from collections.abc import Generator
|
||||||
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,7 +44,8 @@ class ValidationPrinter(StringPrinter):
|
|||||||
self.errors = errors
|
self.errors = errors
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_error_messages(node: str, errors: list[str | dict[str, Any]], current_level: int = 1) -> Iterator[Property]:
|
def get_error_messages(node: str, errors: list[str | dict[str, Any]],
|
||||||
|
current_level: int = 1) -> Generator[Property, None, None]:
|
||||||
"""
|
"""
|
||||||
extract default error message from cerberus class
|
extract default error message from cerberus class
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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) -> Iterator[str]:
|
def packages_update(self, result: Result, target_dir: Path) -> Generator[str, None, None]:
|
||||||
"""
|
"""
|
||||||
update all packages from the build result
|
update all packages from the build result
|
||||||
|
|
||||||
|
|||||||
21
src/ahriman/core/housekeeping/__init__.py
Normal file
21
src/ahriman/core/housekeeping/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#
|
||||||
|
# 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.core.housekeeping.archive_rotation_trigger import ArchiveRotationTrigger
|
||||||
|
from ahriman.core.housekeeping.logs_rotation_trigger import LogsRotationTrigger
|
||||||
115
src/ahriman/core/housekeeping/archive_rotation_trigger.py
Normal file
115
src/ahriman/core/housekeeping/archive_rotation_trigger.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#
|
||||||
|
# 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 collections.abc import Callable
|
||||||
|
from functools import cmp_to_key
|
||||||
|
|
||||||
|
from ahriman.core import context
|
||||||
|
from ahriman.core.alpm.pacman import Pacman
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.triggers import Trigger
|
||||||
|
from ahriman.core.utils import package_like
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
|
class ArchiveRotationTrigger(Trigger):
|
||||||
|
"""
|
||||||
|
remove packages from archive
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
keep_built_packages(int): number of last packages to keep
|
||||||
|
paths(RepositoryPaths): repository paths instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONFIGURATION_SCHEMA = {
|
||||||
|
"archive": {
|
||||||
|
"type": "dict",
|
||||||
|
"schema": {
|
||||||
|
"keep_built_packages": {
|
||||||
|
"type": "integer",
|
||||||
|
"required": True,
|
||||||
|
"coerce": "integer",
|
||||||
|
"min": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
repository_id(RepositoryId): repository unique identifier
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
"""
|
||||||
|
Trigger.__init__(self, repository_id, configuration)
|
||||||
|
|
||||||
|
section = next(iter(self.configuration_sections(configuration)))
|
||||||
|
self.keep_built_packages = max(configuration.getint(section, "keep_built_packages"), 0)
|
||||||
|
self.paths = configuration.repository_paths
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def configuration_sections(cls, configuration: Configuration) -> list[str]:
|
||||||
|
"""
|
||||||
|
extract configuration sections from configuration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: read configuration sections belong to this trigger
|
||||||
|
"""
|
||||||
|
return list(cls.CONFIGURATION_SCHEMA.keys())
|
||||||
|
|
||||||
|
def archives_remove(self, package: Package, pacman: Pacman) -> None:
|
||||||
|
"""
|
||||||
|
remove older versions of the specified package
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package(Package): package which has been updated to check for older versions
|
||||||
|
pacman(Pacman): alpm wrapper instance
|
||||||
|
"""
|
||||||
|
packages: dict[tuple[str, str], Package] = {}
|
||||||
|
# we can't use here load_archives, because it ignores versions
|
||||||
|
for full_path in filter(package_like, self.paths.archive_for(package.base).iterdir()):
|
||||||
|
local = Package.from_archive(full_path, pacman)
|
||||||
|
packages.setdefault((local.base, local.version), local).packages.update(local.packages)
|
||||||
|
|
||||||
|
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
||||||
|
to_remove = sorted(packages.values(), key=cmp_to_key(comparator))
|
||||||
|
for single in to_remove[:-self.keep_built_packages]:
|
||||||
|
self.logger.info("removing version %s of package %s", single.version, single.base)
|
||||||
|
for archive in single.packages.values():
|
||||||
|
for path in self.paths.archive_for(single.base).glob(f"{archive.filename}*"):
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
def on_result(self, result: Result, packages: list[Package]) -> None:
|
||||||
|
"""
|
||||||
|
run trigger
|
||||||
|
|
||||||
|
Args:
|
||||||
|
result(Result): build result
|
||||||
|
packages(list[Package]): list of all available packages
|
||||||
|
"""
|
||||||
|
ctx = context.get()
|
||||||
|
pacman = ctx.get(Pacman)
|
||||||
|
|
||||||
|
for package in result.success:
|
||||||
|
self.archives_remove(package, pacman)
|
||||||
87
src/ahriman/core/housekeeping/logs_rotation_trigger.py
Normal file
87
src/ahriman/core/housekeeping/logs_rotation_trigger.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#
|
||||||
|
# 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.core import context
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.status import Client
|
||||||
|
from ahriman.core.triggers import Trigger
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
|
class LogsRotationTrigger(Trigger):
|
||||||
|
"""
|
||||||
|
rotate logs after build processes
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
keep_last_records(int): number of last records to keep
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONFIGURATION_SCHEMA = {
|
||||||
|
"logs-rotation": {
|
||||||
|
"type": "dict",
|
||||||
|
"schema": {
|
||||||
|
"keep_last_logs": {
|
||||||
|
"type": "integer",
|
||||||
|
"required": True,
|
||||||
|
"coerce": "integer",
|
||||||
|
"min": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
repository_id(RepositoryId): repository unique identifier
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
"""
|
||||||
|
Trigger.__init__(self, repository_id, configuration)
|
||||||
|
|
||||||
|
section = next(iter(self.configuration_sections(configuration)))
|
||||||
|
self.keep_last_records = configuration.getint( # read old-style first and then fallback to new style
|
||||||
|
"settings", "keep_last_logs",
|
||||||
|
fallback=configuration.getint(section, "keep_last_logs"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def configuration_sections(cls, configuration: Configuration) -> list[str]:
|
||||||
|
"""
|
||||||
|
extract configuration sections from configuration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: read configuration sections belong to this trigger
|
||||||
|
"""
|
||||||
|
return list(cls.CONFIGURATION_SCHEMA.keys())
|
||||||
|
|
||||||
|
def on_result(self, result: Result, packages: list[Package]) -> None:
|
||||||
|
"""
|
||||||
|
run trigger
|
||||||
|
|
||||||
|
Args:
|
||||||
|
result(Result): build result
|
||||||
|
packages(list[Package]): list of all available packages
|
||||||
|
"""
|
||||||
|
ctx = context.get()
|
||||||
|
reporter = ctx.get(Client)
|
||||||
|
reporter.logs_rotate(self.keep_last_records)
|
||||||
@@ -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/>.
|
||||||
#
|
#
|
||||||
import atexit
|
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ class HttpLogHandler(logging.Handler):
|
|||||||
method
|
method
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
keep_last_records(int): number of last records to keep
|
|
||||||
reporter(Client): build status reporter instance
|
reporter(Client): build status reporter instance
|
||||||
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
||||||
"""
|
"""
|
||||||
@@ -56,7 +54,6 @@ class HttpLogHandler(logging.Handler):
|
|||||||
|
|
||||||
self.reporter = Client.load(repository_id, configuration, report=report)
|
self.reporter = Client.load(repository_id, configuration, report=report)
|
||||||
self.suppress_errors = suppress_errors
|
self.suppress_errors = suppress_errors
|
||||||
self.keep_last_records = configuration.getint("settings", "keep_last_logs", fallback=0)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Self:
|
def load(cls, repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Self:
|
||||||
@@ -83,7 +80,6 @@ class HttpLogHandler(logging.Handler):
|
|||||||
root.addHandler(handler)
|
root.addHandler(handler)
|
||||||
|
|
||||||
LogRecordId.DEFAULT_PROCESS_ID = str(uuid.uuid4()) # assign default process identifier for log records
|
LogRecordId.DEFAULT_PROCESS_ID = str(uuid.uuid4()) # assign default process identifier for log records
|
||||||
atexit.register(handler.rotate)
|
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@@ -104,9 +100,3 @@ class HttpLogHandler(logging.Handler):
|
|||||||
if self.suppress_errors:
|
if self.suppress_errors:
|
||||||
return
|
return
|
||||||
self.handleError(record)
|
self.handleError(record)
|
||||||
|
|
||||||
def rotate(self) -> None:
|
|
||||||
"""
|
|
||||||
rotate log records, removing older ones
|
|
||||||
"""
|
|
||||||
self.reporter.logs_rotate(self.keep_last_records)
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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) -> Iterator[None]:
|
def in_package_context(self, package_base: str, version: str | None) -> Generator[None, None, None]:
|
||||||
"""
|
"""
|
||||||
execute function while setting package context
|
execute function while setting package context
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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) -> Iterator[ModuleInfo]:
|
def _modules(module_root: Path, prefix: str) -> Generator[ModuleInfo, None, None]:
|
||||||
"""
|
"""
|
||||||
extract available modules from package
|
extract available modules from package
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ def _modules(module_root: Path, prefix: str) -> Iterator[ModuleInfo]:
|
|||||||
yield module_info
|
yield module_info
|
||||||
|
|
||||||
|
|
||||||
def implementations(root_module: ModuleType, base_class: type[T]) -> Iterator[type[T]]:
|
def implementations(root_module: ModuleType, base_class: type[T]) -> Generator[type[T], None, None]:
|
||||||
"""
|
"""
|
||||||
extract implementations of the ``base_class`` from the module
|
extract implementations of the ``base_class`` from the module
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from typing import Any
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.sign.gpg import GPG
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
from ahriman.core.types import Comparable
|
||||||
from ahriman.core.utils import pretty_datetime, pretty_size, utcnow
|
from ahriman.core.utils import pretty_datetime, pretty_size, utcnow
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
@@ -111,7 +112,7 @@ class JinjaTemplate:
|
|||||||
Returns:
|
Returns:
|
||||||
list[dict[str, str]]: sorted content according to comparator defined
|
list[dict[str, str]]: sorted content according to comparator defined
|
||||||
"""
|
"""
|
||||||
comparator: Callable[[dict[str, str]], str] = lambda item: item["filename"]
|
comparator: Callable[[dict[str, str]], Comparable] = lambda item: item["filename"]
|
||||||
return sorted(content, key=comparator)
|
return sorted(content, key=comparator)
|
||||||
|
|
||||||
def make_html(self, result: Result, template_name: Path | str) -> str:
|
def make_html(self, result: Result, template_name: Path | str) -> str:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from ahriman.core.configuration import Configuration
|
|||||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||||
from ahriman.core.report.report import Report
|
from ahriman.core.report.report import Report
|
||||||
from ahriman.core.status import Client
|
from ahriman.core.status import Client
|
||||||
|
from ahriman.core.types import Comparable
|
||||||
from ahriman.models.event import EventType
|
from ahriman.models.event import EventType
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
@@ -86,7 +87,7 @@ class RSS(Report, JinjaTemplate):
|
|||||||
Returns:
|
Returns:
|
||||||
list[dict[str, str]]: sorted content according to comparator defined
|
list[dict[str, str]]: sorted content according to comparator defined
|
||||||
"""
|
"""
|
||||||
comparator: Callable[[dict[str, str]], datetime.datetime] = \
|
comparator: Callable[[dict[str, str]], Comparable] = \
|
||||||
lambda item: parsedate_to_datetime(item["build_date"])
|
lambda item: parsedate_to_datetime(item["build_date"])
|
||||||
return sorted(content, key=comparator, reverse=True)
|
return sorted(content, key=comparator, reverse=True)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from typing import Generator
|
||||||
|
|
||||||
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) -> Iterator[None]:
|
failure: EventType | None = None) -> Generator[None, None, None]:
|
||||||
"""
|
"""
|
||||||
perform action in package context and log event with time elapsed
|
perform action in package context and log event with time elapsed
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Generator, Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ from ahriman.core.build_tools.package_archive import PackageArchive
|
|||||||
from ahriman.core.build_tools.task import Task
|
from ahriman.core.build_tools.task import Task
|
||||||
from ahriman.core.repository.cleaner import Cleaner
|
from ahriman.core.repository.cleaner import Cleaner
|
||||||
from ahriman.core.repository.package_info import PackageInfo
|
from ahriman.core.repository.package_info import PackageInfo
|
||||||
from ahriman.core.utils import safe_filename
|
from ahriman.core.utils import atomic_move, filelock, package_like, safe_filename
|
||||||
from ahriman.models.changes import Changes
|
from ahriman.models.changes import Changes
|
||||||
from ahriman.models.event import EventType
|
from ahriman.models.event import EventType
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
@@ -41,6 +41,141 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
trait for common repository update processes
|
trait for common repository update processes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _archive_lookup(self, package: Package) -> Generator[Path, None, None]:
|
||||||
|
"""
|
||||||
|
check if there is a rebuilt package already
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package(Package): package to check
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Path: list of built packages and signatures if available, empty list otherwise
|
||||||
|
"""
|
||||||
|
archive = self.paths.archive_for(package.base)
|
||||||
|
|
||||||
|
# find all packages which have same version
|
||||||
|
same_version = [
|
||||||
|
built
|
||||||
|
for path in filter(package_like, archive.iterdir())
|
||||||
|
if (built := Package.from_archive(path, self.pacman)).version == package.version
|
||||||
|
]
|
||||||
|
# no packages of the same version found
|
||||||
|
if not same_version:
|
||||||
|
return
|
||||||
|
|
||||||
|
packages = [single for built in same_version for single in built.packages.values()]
|
||||||
|
# all packages must be either any or same architecture
|
||||||
|
if not all(single.architecture in ("any", self.architecture) for single in packages):
|
||||||
|
return
|
||||||
|
|
||||||
|
for single in packages:
|
||||||
|
yield from archive.glob(f"{single.filename}*")
|
||||||
|
|
||||||
|
def _archive_rename(self, description: PackageDescription, package_base: str) -> None:
|
||||||
|
"""
|
||||||
|
rename package archive removing special symbols
|
||||||
|
|
||||||
|
Args:
|
||||||
|
description(PackageDescription): package description
|
||||||
|
package_base(str): package base name
|
||||||
|
"""
|
||||||
|
if description.filename is None:
|
||||||
|
self.logger.warning("received empty package filename for base %s", package_base)
|
||||||
|
return # suppress type checking, it never can be none actually
|
||||||
|
|
||||||
|
if (safe := safe_filename(description.filename)) != description.filename:
|
||||||
|
atomic_move(self.paths.packages / description.filename, self.paths.packages / safe)
|
||||||
|
description.filename = safe
|
||||||
|
|
||||||
|
def _package_build(self, package: Package, path: Path, packager: str | None,
|
||||||
|
local_version: str | None) -> str | None:
|
||||||
|
"""
|
||||||
|
build single package
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package(Package): package to build
|
||||||
|
path(Path): path to directory with package files
|
||||||
|
packager(str | None): packager identifier used for this package
|
||||||
|
local_version(str | None): local version of the package
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str | None: current commit sha if available
|
||||||
|
"""
|
||||||
|
self.reporter.set_building(package.base)
|
||||||
|
|
||||||
|
task = Task(package, self.configuration, self.architecture, self.paths)
|
||||||
|
patches = self.reporter.package_patches_get(package.base, None)
|
||||||
|
commit_sha = task.init(path, patches, local_version)
|
||||||
|
|
||||||
|
loaded_package = Package.from_build(path, self.architecture, None)
|
||||||
|
if prebuilt := list(self._archive_lookup(loaded_package)):
|
||||||
|
self.logger.info("using prebuilt packages for %s-%s", loaded_package.base, loaded_package.version)
|
||||||
|
built = []
|
||||||
|
for artefact in prebuilt:
|
||||||
|
with filelock(artefact):
|
||||||
|
shutil.copy(artefact, path)
|
||||||
|
built.append(path / artefact.name)
|
||||||
|
else:
|
||||||
|
built = task.build(path, PACKAGER=packager)
|
||||||
|
|
||||||
|
package.with_packages(built, self.pacman)
|
||||||
|
for src in built:
|
||||||
|
dst = self.paths.packages / src.name
|
||||||
|
atomic_move(src, dst)
|
||||||
|
|
||||||
|
return commit_sha
|
||||||
|
|
||||||
|
def _package_remove(self, package_name: str, path: Path) -> None:
|
||||||
|
"""
|
||||||
|
remove single package from repository
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package_name(str): package name
|
||||||
|
path(Path): path to package archive
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.repo.remove(package_name, path)
|
||||||
|
except Exception:
|
||||||
|
self.logger.exception("could not remove %s", package_name)
|
||||||
|
|
||||||
|
def _package_remove_base(self, package_base: str) -> None:
|
||||||
|
"""
|
||||||
|
remove package base from repository
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package_base(str): package base name:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self.in_event(package_base, EventType.PackageRemoved):
|
||||||
|
self.reporter.package_remove(package_base)
|
||||||
|
except Exception:
|
||||||
|
self.logger.exception("could not remove base %s", package_base)
|
||||||
|
|
||||||
|
def _package_update(self, filename: str | None, package_base: str, packager_key: str | None) -> None:
|
||||||
|
"""
|
||||||
|
update built package in repository database
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename(str | None): archive filename
|
||||||
|
package_base(str): package base name
|
||||||
|
packager_key(str | None): packager key identifier
|
||||||
|
"""
|
||||||
|
if filename is None:
|
||||||
|
self.logger.warning("received empty package filename for base %s", package_base)
|
||||||
|
return # suppress type checking, it never can be none actually
|
||||||
|
|
||||||
|
# in theory, it might be NOT packages directory, but we suppose it is
|
||||||
|
full_path = self.paths.packages / filename
|
||||||
|
files = self.sign.process_sign_package(full_path, packager_key)
|
||||||
|
|
||||||
|
for src in files:
|
||||||
|
dst = self.paths.archive_for(package_base) / src.name
|
||||||
|
atomic_move(src, dst) # move package to archive directory
|
||||||
|
if not (symlink := self.paths.repository / dst.name).exists():
|
||||||
|
symlink.symlink_to(dst.relative_to(symlink.parent, walk_up=True)) # create link to archive
|
||||||
|
|
||||||
|
self.repo.add(self.paths.repository / filename)
|
||||||
|
|
||||||
def process_build(self, updates: Iterable[Package], packagers: Packagers | None = None, *,
|
def process_build(self, updates: Iterable[Package], packagers: Packagers | None = None, *,
|
||||||
bump_pkgrel: bool = False) -> Result:
|
bump_pkgrel: bool = False) -> Result:
|
||||||
"""
|
"""
|
||||||
@@ -55,21 +190,6 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
Returns:
|
Returns:
|
||||||
Result: build result
|
Result: build result
|
||||||
"""
|
"""
|
||||||
def build_single(package: Package, local_path: Path, packager_id: str | None) -> str | None:
|
|
||||||
self.reporter.set_building(package.base)
|
|
||||||
task = Task(package, self.configuration, self.architecture, self.paths)
|
|
||||||
local_version = local_versions.get(package.base) if bump_pkgrel else None
|
|
||||||
patches = self.reporter.package_patches_get(package.base, None)
|
|
||||||
commit_sha = task.init(local_path, patches, local_version)
|
|
||||||
built = task.build(local_path, PACKAGER=packager_id)
|
|
||||||
|
|
||||||
package.with_packages(built, self.pacman)
|
|
||||||
for src in built:
|
|
||||||
dst = self.paths.packages / src.name
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
return commit_sha
|
|
||||||
|
|
||||||
packagers = packagers or Packagers()
|
packagers = packagers or Packagers()
|
||||||
local_versions = {package.base: package.version for package in self.packages()}
|
local_versions = {package.base: package.version for package in self.packages()}
|
||||||
|
|
||||||
@@ -80,16 +200,21 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
try:
|
try:
|
||||||
with self.in_event(single.base, EventType.PackageUpdated, failure=EventType.PackageUpdateFailed):
|
with self.in_event(single.base, EventType.PackageUpdated, failure=EventType.PackageUpdateFailed):
|
||||||
packager = self.packager(packagers, single.base)
|
packager = self.packager(packagers, single.base)
|
||||||
last_commit_sha = build_single(single, Path(dir_name), packager.packager_id)
|
local_version = local_versions.get(single.base) if bump_pkgrel else None
|
||||||
|
commit_sha = self._package_build(single, Path(dir_name), packager.packager_id, local_version)
|
||||||
|
|
||||||
# update commit hash for changes keeping current diff if there is any
|
# update commit hash for changes keeping current diff if there is any
|
||||||
changes = self.reporter.package_changes_get(single.base)
|
changes = self.reporter.package_changes_get(single.base)
|
||||||
self.reporter.package_changes_update(single.base, Changes(last_commit_sha, changes.changes))
|
self.reporter.package_changes_update(single.base, Changes(commit_sha, changes.changes))
|
||||||
|
|
||||||
# update dependencies list
|
# update dependencies list
|
||||||
package_archive = PackageArchive(self.paths.build_root, single, self.pacman, self.scan_paths)
|
package_archive = PackageArchive(self.paths.build_root, single, self.pacman, self.scan_paths)
|
||||||
dependencies = package_archive.depends_on()
|
dependencies = package_archive.depends_on()
|
||||||
self.reporter.package_dependencies_update(single.base, dependencies)
|
self.reporter.package_dependencies_update(single.base, dependencies)
|
||||||
|
|
||||||
# update result set
|
# update result set
|
||||||
result.add_updated(single)
|
result.add_updated(single)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.reporter.set_failed(single.base)
|
self.reporter.set_failed(single.base)
|
||||||
result.add_failed(single)
|
result.add_failed(single)
|
||||||
@@ -107,19 +232,6 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
Returns:
|
Returns:
|
||||||
Result: remove result
|
Result: remove result
|
||||||
"""
|
"""
|
||||||
def remove_base(package_base: str) -> None:
|
|
||||||
try:
|
|
||||||
with self.in_event(package_base, EventType.PackageRemoved):
|
|
||||||
self.reporter.package_remove(package_base)
|
|
||||||
except Exception:
|
|
||||||
self.logger.exception("could not remove base %s", package_base)
|
|
||||||
|
|
||||||
def remove_package(package: str, archive_path: Path) -> None:
|
|
||||||
try:
|
|
||||||
self.repo.remove(package, archive_path) # remove the package itself
|
|
||||||
except Exception:
|
|
||||||
self.logger.exception("could not remove %s", package)
|
|
||||||
|
|
||||||
packages_to_remove: dict[str, Path] = {}
|
packages_to_remove: dict[str, Path] = {}
|
||||||
bases_to_remove: list[str] = []
|
bases_to_remove: list[str] = []
|
||||||
|
|
||||||
@@ -136,6 +248,7 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
})
|
})
|
||||||
bases_to_remove.append(local.base)
|
bases_to_remove.append(local.base)
|
||||||
result.add_removed(local)
|
result.add_removed(local)
|
||||||
|
|
||||||
elif requested.intersection(local.packages.keys()):
|
elif requested.intersection(local.packages.keys()):
|
||||||
packages_to_remove.update({
|
packages_to_remove.update({
|
||||||
package: properties.filepath
|
package: properties.filepath
|
||||||
@@ -152,11 +265,11 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
|
|
||||||
# remove packages from repository files
|
# remove packages from repository files
|
||||||
for package, filename in packages_to_remove.items():
|
for package, filename in packages_to_remove.items():
|
||||||
remove_package(package, filename)
|
self._package_remove(package, filename)
|
||||||
|
|
||||||
# remove bases from registered
|
# remove bases from registered
|
||||||
for package in bases_to_remove:
|
for package in bases_to_remove:
|
||||||
remove_base(package)
|
self._package_remove_base(package)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -172,27 +285,6 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
Returns:
|
Returns:
|
||||||
Result: path to repository database
|
Result: path to repository database
|
||||||
"""
|
"""
|
||||||
def rename(archive: PackageDescription, package_base: str) -> None:
|
|
||||||
if archive.filename is None:
|
|
||||||
self.logger.warning("received empty package name for base %s", package_base)
|
|
||||||
return # suppress type checking, it never can be none actually
|
|
||||||
if (safe := safe_filename(archive.filename)) != archive.filename:
|
|
||||||
shutil.move(self.paths.packages / archive.filename, self.paths.packages / safe)
|
|
||||||
archive.filename = safe
|
|
||||||
|
|
||||||
def update_single(name: str | None, package_base: str, packager_key: str | None) -> None:
|
|
||||||
if name is None:
|
|
||||||
self.logger.warning("received empty package name for base %s", package_base)
|
|
||||||
return # suppress type checking, it never can be none actually
|
|
||||||
# in theory, it might be NOT packages directory, but we suppose it is
|
|
||||||
full_path = self.paths.packages / name
|
|
||||||
files = self.sign.process_sign_package(full_path, packager_key)
|
|
||||||
for src in files:
|
|
||||||
dst = self.paths.repository / safe_filename(src.name)
|
|
||||||
shutil.move(src, dst)
|
|
||||||
package_path = self.paths.repository / safe_filename(name)
|
|
||||||
self.repo.add(package_path)
|
|
||||||
|
|
||||||
current_packages = {package.base: package for package in self.packages()}
|
current_packages = {package.base: package for package in self.packages()}
|
||||||
local_versions = {package_base: package.version for package_base, package in current_packages.items()}
|
local_versions = {package_base: package.version for package_base, package in current_packages.items()}
|
||||||
|
|
||||||
@@ -207,8 +299,8 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
packager = self.packager(packagers, local.base)
|
packager = self.packager(packagers, local.base)
|
||||||
|
|
||||||
for description in local.packages.values():
|
for description in local.packages.values():
|
||||||
rename(description, local.base)
|
self._archive_rename(description, local.base)
|
||||||
update_single(description.filename, local.base, packager.key)
|
self._package_update(description.filename, local.base, packager.key)
|
||||||
self.reporter.set_success(local)
|
self.reporter.set_success(local)
|
||||||
result.add_updated(local)
|
result.add_updated(local)
|
||||||
|
|
||||||
@@ -216,12 +308,13 @@ class Executor(PackageInfo, Cleaner):
|
|||||||
if local.base in current_packages:
|
if local.base in current_packages:
|
||||||
current_package_archives = set(current_packages[local.base].packages.keys())
|
current_package_archives = set(current_packages[local.base].packages.keys())
|
||||||
removed_packages.extend(current_package_archives.difference(local.packages))
|
removed_packages.extend(current_package_archives.difference(local.packages))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.reporter.set_failed(local.base)
|
self.reporter.set_failed(local.base)
|
||||||
result.add_failed(local)
|
result.add_failed(local)
|
||||||
self.logger.exception("could not process %s", local.base)
|
self.logger.exception("could not process %s", local.base)
|
||||||
self.clear_packages()
|
|
||||||
|
|
||||||
|
self.clear_packages()
|
||||||
self.process_remove(removed_packages)
|
self.process_remove(removed_packages)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ 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
|
||||||
@@ -203,12 +208,15 @@ 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, limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
||||||
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -152,19 +152,22 @@ 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, limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
||||||
|
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, limit, offset, self.repository_id)
|
return self.database.logs_get(package_base, version, process_id, 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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class Watcher(LazyLogging):
|
|||||||
|
|
||||||
package_logs_add: Callable[[LogRecord], None]
|
package_logs_add: Callable[[LogRecord], None]
|
||||||
|
|
||||||
package_logs_get: Callable[[str, int, int], list[LogRecord]]
|
package_logs_get: Callable[[str, str | None, str | None, int, int], list[LogRecord]]
|
||||||
|
|
||||||
package_logs_remove: Callable[[str, str | None], None]
|
package_logs_remove: Callable[[str, str | None], None]
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +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/>.
|
||||||
#
|
#
|
||||||
|
# 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
|
||||||
@@ -165,6 +166,13 @@ 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
|
||||||
@@ -326,12 +334,15 @@ 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, limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -339,6 +350,10 @@ 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)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from collections.abc import Callable, Iterator
|
from collections.abc import Callable, Generator
|
||||||
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() -> Iterator[tuple[str, str]]:
|
def sources_generator() -> Generator[tuple[str, str], None, None]:
|
||||||
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)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class Trigger(LazyLogging):
|
|||||||
CONFIGURATION_SCHEMA(ConfigurationSchema): (class attribute) configuration schema template
|
CONFIGURATION_SCHEMA(ConfigurationSchema): (class attribute) configuration schema template
|
||||||
CONFIGURATION_SCHEMA_FALLBACK(str | None): (class attribute) optional fallback option for defining
|
CONFIGURATION_SCHEMA_FALLBACK(str | None): (class attribute) optional fallback option for defining
|
||||||
configuration schema type used
|
configuration schema type used
|
||||||
|
REQUIRES_REPOSITORY(bool): (class attribute) either trigger requires loaded repository or not
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
repository_id(RepositoryId): repository unique identifier
|
repository_id(RepositoryId): repository unique identifier
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ class Trigger(LazyLogging):
|
|||||||
|
|
||||||
CONFIGURATION_SCHEMA: ClassVar[ConfigurationSchema] = {}
|
CONFIGURATION_SCHEMA: ClassVar[ConfigurationSchema] = {}
|
||||||
CONFIGURATION_SCHEMA_FALLBACK: ClassVar[str | None] = None
|
CONFIGURATION_SCHEMA_FALLBACK: ClassVar[str | None] = None
|
||||||
|
REQUIRES_REPOSITORY: ClassVar[bool] = True
|
||||||
|
|
||||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -79,6 +81,16 @@ class Trigger(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
return self.repository_id.architecture
|
return self.repository_id.architecture
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_allowed_to_run(self) -> bool:
|
||||||
|
"""
|
||||||
|
whether trigger allowed to run or not
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: ``True`` in case if trigger allowed to run and ``False`` otherwise
|
||||||
|
"""
|
||||||
|
return not (self.REQUIRES_REPOSITORY and self.repository_id.is_empty)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configuration_schema(cls, configuration: Configuration | None) -> ConfigurationSchema:
|
def configuration_schema(cls, configuration: Configuration | None) -> ConfigurationSchema:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -17,10 +17,11 @@
|
|||||||
# 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 atexit
|
||||||
import contextlib
|
import contextlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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
|
||||||
@@ -60,17 +61,8 @@ class TriggerLoader(LazyLogging):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
""""""
|
""""""
|
||||||
self._on_stop_requested = False
|
|
||||||
self.triggers: list[Trigger] = []
|
self.triggers: list[Trigger] = []
|
||||||
|
|
||||||
def __del__(self) -> None:
|
|
||||||
"""
|
|
||||||
custom destructor object which calls on_stop in case if it was requested
|
|
||||||
"""
|
|
||||||
if not self._on_stop_requested:
|
|
||||||
return
|
|
||||||
self.on_stop()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, repository_id: RepositoryId, configuration: Configuration) -> Self:
|
def load(cls, repository_id: RepositoryId, configuration: Configuration) -> Self:
|
||||||
"""
|
"""
|
||||||
@@ -85,8 +77,9 @@ class TriggerLoader(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
instance = cls()
|
instance = cls()
|
||||||
instance.triggers = [
|
instance.triggers = [
|
||||||
instance.load_trigger(trigger, repository_id, configuration)
|
trigger
|
||||||
for trigger in instance.selected_triggers(configuration)
|
for trigger_name in instance.selected_triggers(configuration)
|
||||||
|
if (trigger := instance.load_trigger(trigger_name, repository_id, configuration)).is_allowed_to_run
|
||||||
]
|
]
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
@@ -119,7 +112,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) -> Iterator[None]:
|
def __execute_trigger(self, trigger: Trigger) -> Generator[None, None, None]:
|
||||||
"""
|
"""
|
||||||
decorator for calling triggers
|
decorator for calling triggers
|
||||||
|
|
||||||
@@ -250,10 +243,11 @@ class TriggerLoader(LazyLogging):
|
|||||||
run triggers on load
|
run triggers on load
|
||||||
"""
|
"""
|
||||||
self.logger.debug("executing triggers on start")
|
self.logger.debug("executing triggers on start")
|
||||||
self._on_stop_requested = True
|
|
||||||
for trigger in self.triggers:
|
for trigger in self.triggers:
|
||||||
with self.__execute_trigger(trigger):
|
with self.__execute_trigger(trigger):
|
||||||
trigger.on_start()
|
trigger.on_start()
|
||||||
|
# register on_stop call
|
||||||
|
atexit.register(self.on_stop)
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
def on_stop(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -17,7 +17,15 @@
|
|||||||
# 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 typing import Protocol
|
from typing import Any, Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class Comparable(Protocol):
|
||||||
|
"""
|
||||||
|
class which supports :func:`__lt__` operation`
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __lt__(self, other: Any) -> bool: ...
|
||||||
|
|
||||||
|
|
||||||
class HasBool(Protocol):
|
class HasBool(Protocol):
|
||||||
|
|||||||
@@ -18,16 +18,19 @@
|
|||||||
# 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-lines
|
# pylint: disable=too-many-lines
|
||||||
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
|
import fcntl
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import selectors
|
import selectors
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from collections.abc import Callable, Iterable, Iterator, Mapping
|
from collections.abc import Callable, Generator, Iterable, 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,25 +38,27 @@ 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__ = [
|
||||||
|
"atomic_move",
|
||||||
"check_output",
|
"check_output",
|
||||||
"check_user",
|
"check_user",
|
||||||
"dataclass_view",
|
"dataclass_view",
|
||||||
"enum_values",
|
"enum_values",
|
||||||
"extract_user",
|
"extract_user",
|
||||||
|
"filelock",
|
||||||
"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",
|
||||||
@@ -65,6 +70,25 @@ __all__ = [
|
|||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
def atomic_move(src: Path, dst: Path) -> None:
|
||||||
|
"""
|
||||||
|
move file from ``source`` location to ``destination``. This method uses lock and :func:`shutil.move` to ensure that
|
||||||
|
file will be copied (if not rename) atomically. This method blocks execution until lock is available
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src(Path): path to the source file
|
||||||
|
dst(Path): path to the destination
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
This method is a drop-in replacement for :func:`shutil.move` (except it doesn't allow to override copy method)
|
||||||
|
which first locking destination file. To use it simply call method with arguments::
|
||||||
|
|
||||||
|
>>> atomic_move(src, dst)
|
||||||
|
"""
|
||||||
|
with filelock(dst):
|
||||||
|
shutil.move(src, dst)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
def check_output(*args: str, exception: Exception | Callable[[int, list[str], str, str], Exception] | None = None,
|
def check_output(*args: str, exception: Exception | Callable[[int, list[str], str, str], Exception] | None = None,
|
||||||
cwd: Path | None = None, input_data: str | None = None,
|
cwd: Path | None = None, input_data: str | None = None,
|
||||||
@@ -113,7 +137,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) -> Iterator[tuple[str, str]]:
|
def poll(sel: selectors.BaseSelector) -> Generator[tuple[str, str], None, None]:
|
||||||
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,13 +194,12 @@ def check_output(*args: str, exception: Exception | Callable[[int, list[str], st
|
|||||||
return stdout
|
return stdout
|
||||||
|
|
||||||
|
|
||||||
def check_user(root: Path, *, unsafe: bool) -> None:
|
def check_user(paths: RepositoryPaths, *, unsafe: bool) -> None:
|
||||||
"""
|
"""
|
||||||
check if current user is the owner of the root
|
check if current user is the owner of the root
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
root(Path): path to root directory (e.g. repository root
|
paths(RepositoryPaths): repository paths object
|
||||||
: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:
|
||||||
@@ -185,16 +208,14 @@ def check_user(root: Path, *, unsafe: bool) -> None:
|
|||||||
Examples:
|
Examples:
|
||||||
Simply run function with arguments::
|
Simply run function with arguments::
|
||||||
|
|
||||||
>>> check_user(root, unsafe=False)
|
>>> check_user(paths, unsafe=False)
|
||||||
"""
|
"""
|
||||||
if not root.exists():
|
if not paths.root.exists():
|
||||||
return # no directory found, skip check
|
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, _ = owner(root)
|
root_uid, _ = paths.root_owner
|
||||||
|
|
||||||
if current_uid != root_uid:
|
if current_uid != root_uid:
|
||||||
raise UnsafeRunError(current_uid, root_uid)
|
raise UnsafeRunError(current_uid, root_uid)
|
||||||
|
|
||||||
@@ -236,6 +257,27 @@ def extract_user() -> str | None:
|
|||||||
return os.getenv("SUDO_USER") or os.getenv("DOAS_USER") or os.getenv("USER")
|
return os.getenv("SUDO_USER") or os.getenv("DOAS_USER") or os.getenv("USER")
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def filelock(path: Path) -> Generator[None, None, None]:
|
||||||
|
"""
|
||||||
|
lock on file passed as argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path(Path): path object on which lock must be performed
|
||||||
|
"""
|
||||||
|
lock_path = path.with_name(f".{path.name}")
|
||||||
|
try:
|
||||||
|
with lock_path.open("ab") as lock_file:
|
||||||
|
fd = lock_file.fileno()
|
||||||
|
try:
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_EX) # lock file and wait lock is until available
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_UN) # unlock file first
|
||||||
|
finally:
|
||||||
|
lock_path.unlink(missing_ok=True) # remove lock file at the end
|
||||||
|
|
||||||
|
|
||||||
def filter_json(source: dict[str, Any], known_fields: Iterable[str]) -> dict[str, Any]:
|
def filter_json(source: dict[str, Any], known_fields: Iterable[str]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
filter json object by fields used for json-to-object conversion
|
filter json object by fields used for json-to-object conversion
|
||||||
@@ -292,20 +334,6 @@ def minmax(source: Iterable[T], *, key: Callable[[T], Any] | None = None) -> tup
|
|||||||
return min(first_iter, key=key), max(second_iter, key=key) # type: ignore
|
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
|
||||||
@@ -371,6 +399,28 @@ 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
|
||||||
@@ -426,22 +476,6 @@ 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:
|
||||||
"""
|
"""
|
||||||
@@ -510,7 +544,7 @@ def utcnow() -> datetime.datetime:
|
|||||||
return datetime.datetime.now(datetime.UTC)
|
return datetime.datetime.now(datetime.UTC)
|
||||||
|
|
||||||
|
|
||||||
def walk(directory_path: Path) -> Iterator[Path]:
|
def walk(directory_path: Path) -> Generator[Path, None, None]:
|
||||||
"""
|
"""
|
||||||
list all file paths in given directory
|
list all file paths in given directory
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from collections.abc import Callable, Iterable, Iterator
|
from collections.abc import Callable, Generator, Iterable
|
||||||
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 or package.name,
|
base=package.base,
|
||||||
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) -> Iterator[Path]:
|
def local_files(path: Path) -> Generator[Path, None, None]:
|
||||||
"""
|
"""
|
||||||
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() -> Iterator[str]:
|
def generator() -> Generator[str, None, None]:
|
||||||
for package in self.packages.values():
|
for package in self.packages.values():
|
||||||
yield from extractor(package)
|
yield from extractor(package)
|
||||||
|
|
||||||
@@ -520,8 +520,7 @@ class Package(LazyLogging):
|
|||||||
else:
|
else:
|
||||||
remote_version = remote.version
|
remote_version = remote.version
|
||||||
|
|
||||||
result: int = vercmp(self.version, remote_version)
|
return self.vercmp(remote_version) < 0
|
||||||
return result < 0
|
|
||||||
|
|
||||||
def next_pkgrel(self, local_version: str | None) -> str | None:
|
def next_pkgrel(self, local_version: str | None) -> str | None:
|
||||||
"""
|
"""
|
||||||
@@ -540,7 +539,7 @@ class Package(LazyLogging):
|
|||||||
if local_version is None:
|
if local_version is None:
|
||||||
return None # local version not found, keep upstream pkgrel
|
return None # local version not found, keep upstream pkgrel
|
||||||
|
|
||||||
if vercmp(self.version, local_version) > 0:
|
if self.vercmp(local_version) > 0:
|
||||||
return None # upstream version is newer than local one, keep upstream pkgrel
|
return None # upstream version is newer than local one, keep upstream pkgrel
|
||||||
|
|
||||||
*_, local_pkgrel = parse_version(local_version)
|
*_, local_pkgrel = parse_version(local_version)
|
||||||
@@ -561,6 +560,19 @@ class Package(LazyLogging):
|
|||||||
details = "" if self.is_single_package else f""" ({" ".join(sorted(self.packages.keys()))})"""
|
details = "" if self.is_single_package else f""" ({" ".join(sorted(self.packages.keys()))})"""
|
||||||
return f"{self.base}{details}"
|
return f"{self.base}{details}"
|
||||||
|
|
||||||
|
def vercmp(self, version: str) -> int:
|
||||||
|
"""
|
||||||
|
typed wrapper around :func:`pyalpm.vercmp()`
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version(str): version to compare
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: negative if current version is less than provided, positive if greater than and zero if equals
|
||||||
|
"""
|
||||||
|
result: int = vercmp(self.version, version)
|
||||||
|
return result
|
||||||
|
|
||||||
def view(self) -> dict[str, Any]:
|
def view(self) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
generate json package view
|
generate json package view
|
||||||
@@ -570,7 +582,7 @@ class Package(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
return dataclass_view(self)
|
return dataclass_view(self)
|
||||||
|
|
||||||
def with_packages(self, packages: Iterable[Path], pacman: Pacman) -> None:
|
def with_packages(self, packages: list[Path], pacman: Pacman) -> None:
|
||||||
"""
|
"""
|
||||||
replace packages descriptions with ones from archives
|
replace packages descriptions with ones from archives
|
||||||
|
|
||||||
|
|||||||
@@ -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, Iterator, Self
|
from typing import Any, Generator, 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() -> Iterator[str]:
|
def generator() -> Generator[str, None, None]:
|
||||||
token = None
|
token = None
|
||||||
for char in source:
|
for char in source:
|
||||||
if token is not None:
|
if token is not None:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import contextlib
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
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,7 +29,6 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@@ -86,6 +85,16 @@ class RepositoryPaths(LazyLogging):
|
|||||||
return Path(self.repository_id.architecture) # legacy tree suffix
|
return Path(self.repository_id.architecture) # legacy tree suffix
|
||||||
return Path(self.repository_id.name) / self.repository_id.architecture
|
return Path(self.repository_id.name) / self.repository_id.architecture
|
||||||
|
|
||||||
|
@property
|
||||||
|
def archive(self) -> Path:
|
||||||
|
"""
|
||||||
|
archive directory root
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: archive directory root
|
||||||
|
"""
|
||||||
|
return self.root / "archive"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def build_root(self) -> Path:
|
def build_root(self) -> Path:
|
||||||
"""
|
"""
|
||||||
@@ -94,7 +103,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, _ = owner(self.root)
|
uid, _ = self.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
|
||||||
@@ -156,7 +165,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 owner(self.root)
|
return self.owner(self.root)
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -171,7 +180,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) -> Iterator[str]:
|
def walk(repository_dir: Path) -> Generator[str, None, None]:
|
||||||
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
|
||||||
|
|
||||||
@@ -198,7 +207,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) -> Iterator[str]:
|
def walk(paths: RepositoryPaths) -> Generator[str, None, None]:
|
||||||
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
|
||||||
@@ -209,12 +218,26 @@ 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
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
More likely you don't want to call this method explicitly, consider using :func:`preserve_owner`
|
More likely you don't want to call this method explicitly, consider using :func:`preserve_owner()`
|
||||||
as context manager instead
|
as context manager instead
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -224,7 +247,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 = owner(current)
|
uid, gid = self.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)
|
||||||
@@ -236,6 +259,23 @@ class RepositoryPaths(LazyLogging):
|
|||||||
set_owner(path)
|
set_owner(path)
|
||||||
path = path.parent
|
path = path.parent
|
||||||
|
|
||||||
|
def archive_for(self, package_base: str) -> Path:
|
||||||
|
"""
|
||||||
|
get path to archive specified search criteria
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package_base(str): package base name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: path to archive directory for package base
|
||||||
|
"""
|
||||||
|
directory = self.archive / "packages" / package_base[0] / package_base
|
||||||
|
if not directory.is_dir(): # create if not exists
|
||||||
|
with self.preserve_owner(self.archive):
|
||||||
|
directory.mkdir(mode=0o755, parents=True)
|
||||||
|
|
||||||
|
return directory
|
||||||
|
|
||||||
def cache_for(self, package_base: str) -> Path:
|
def cache_for(self, package_base: str) -> Path:
|
||||||
"""
|
"""
|
||||||
get path to cached PKGBUILD and package sources for the package base
|
get path to cached PKGBUILD and package sources for the package base
|
||||||
@@ -249,7 +289,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) -> Iterator[None]:
|
def preserve_owner(self, path: Path | None = None) -> Generator[None, None, 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
|
||||||
|
|
||||||
@@ -268,12 +308,16 @@ class RepositoryPaths(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
path = path or self.root
|
path = path or self.root
|
||||||
|
|
||||||
def walk(root: Path) -> Iterator[Path]:
|
def walk(root: Path) -> Generator[Path, None, None]:
|
||||||
|
yield root
|
||||||
|
if not root.exists():
|
||||||
|
return
|
||||||
|
|
||||||
# basically walk, but skipping some content
|
# basically walk, but skipping some content
|
||||||
for child in safe_iterdir(root):
|
for child in root.iterdir():
|
||||||
yield child
|
yield child
|
||||||
if child in (self.chroot.parent,):
|
if child in (self.chroot.parent,):
|
||||||
yield from safe_iterdir(child) # we only yield top-level in chroot directory
|
yield from child.iterdir() # we only yield top-level in chroot directory
|
||||||
elif child.is_dir():
|
elif child.is_dir():
|
||||||
yield from walk(child)
|
yield from walk(child)
|
||||||
|
|
||||||
@@ -307,6 +351,7 @@ class RepositoryPaths(LazyLogging):
|
|||||||
|
|
||||||
with self.preserve_owner():
|
with self.preserve_owner():
|
||||||
for directory in (
|
for directory in (
|
||||||
|
self.archive,
|
||||||
self.cache,
|
self.cache,
|
||||||
self.chroot,
|
self.chroot,
|
||||||
self.packages,
|
self.packages,
|
||||||
|
|||||||
@@ -21,8 +21,18 @@ import aiohttp_jinja2
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp.typedefs import Middleware
|
from aiohttp.typedefs import Middleware
|
||||||
from aiohttp.web import HTTPClientError, HTTPException, HTTPMethodNotAllowed, HTTPNoContent, HTTPServerError, \
|
from aiohttp.web import (
|
||||||
HTTPUnauthorized, Request, StreamResponse, json_response, middleware
|
HTTPClientError,
|
||||||
|
HTTPException,
|
||||||
|
HTTPMethodNotAllowed,
|
||||||
|
HTTPNoContent,
|
||||||
|
HTTPServerError,
|
||||||
|
HTTPUnauthorized,
|
||||||
|
Request,
|
||||||
|
StreamResponse,
|
||||||
|
json_response,
|
||||||
|
middleware,
|
||||||
|
)
|
||||||
|
|
||||||
from ahriman.web.middlewares import HandlerType
|
from ahriman.web.middlewares import HandlerType
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from aiohttp.web import Application, View
|
from aiohttp.web import Application, View
|
||||||
from collections.abc import Iterator
|
from collections.abc import Generator
|
||||||
|
|
||||||
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) -> Iterator[tuple[str, type[View]]]:
|
def _dynamic_routes(configuration: Configuration) -> Generator[tuple[str, type[View]], None, None]:
|
||||||
"""
|
"""
|
||||||
extract dynamic routes based on views
|
extract dynamic routes based on views
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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
|
||||||
@@ -34,6 +35,7 @@ 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
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ class AURPackageSchema(Schema):
|
|||||||
response AUR package schema
|
response AUR package schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
package = fields.String(required=True, metadata={
|
|
||||||
"description": "Package base",
|
|
||||||
"example": "ahriman",
|
|
||||||
})
|
|
||||||
description = fields.String(required=True, metadata={
|
description = fields.String(required=True, metadata={
|
||||||
"description": "Package description",
|
"description": "Package description",
|
||||||
"example": "ArcH linux ReposItory MANager",
|
"example": "ArcH linux ReposItory MANager",
|
||||||
})
|
})
|
||||||
|
package = fields.String(required=True, metadata={
|
||||||
|
"description": "Package base",
|
||||||
|
"example": "ahriman",
|
||||||
|
})
|
||||||
|
|||||||
@@ -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",
|
|
||||||
})
|
|
||||||
|
|||||||
39
src/ahriman/web/schemas/configuration_schema.py
Normal file
39
src/ahriman/web/schemas/configuration_schema.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#
|
||||||
|
# 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",
|
||||||
|
})
|
||||||
@@ -25,18 +25,6 @@ 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,
|
||||||
@@ -45,7 +33,19 @@ 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,
|
||||||
|
})
|
||||||
|
|||||||
@@ -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",
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ class EventSearchSchema(PaginationSchema):
|
|||||||
"description": "Event type",
|
"description": "Event type",
|
||||||
"example": EventType.PackageUpdated,
|
"example": EventType.PackageUpdated,
|
||||||
})
|
})
|
||||||
object_id = fields.String(metadata={
|
|
||||||
"description": "Event object identifier",
|
|
||||||
"example": "ahriman",
|
|
||||||
})
|
|
||||||
from_date = fields.Integer(metadata={
|
from_date = fields.Integer(metadata={
|
||||||
"description": "Minimal creation timestamp, inclusive",
|
"description": "Minimal creation timestamp, inclusive",
|
||||||
"example": 1680537091,
|
"example": 1680537091,
|
||||||
})
|
})
|
||||||
|
object_id = fields.String(metadata={
|
||||||
|
"description": "Event object identifier",
|
||||||
|
"example": "ahriman",
|
||||||
|
})
|
||||||
to_date = fields.Integer(metadata={
|
to_date = fields.Integer(metadata={
|
||||||
"description": "Maximal creation timestamp, exclusive",
|
"description": "Maximal creation timestamp, exclusive",
|
||||||
"example": 1680537091,
|
"example": 1680537091,
|
||||||
|
|||||||
@@ -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",
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ class LoginSchema(Schema):
|
|||||||
request login schema
|
request login schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
username = fields.String(required=True, metadata={
|
|
||||||
"description": "Login username",
|
|
||||||
"example": "user",
|
|
||||||
})
|
|
||||||
password = fields.String(required=True, metadata={
|
password = fields.String(required=True, metadata={
|
||||||
"description": "Login password",
|
"description": "Login password",
|
||||||
"example": "pa55w0rd",
|
"example": "pa55w0rd",
|
||||||
})
|
})
|
||||||
|
username = fields.String(required=True, metadata={
|
||||||
|
"description": "Login username",
|
||||||
|
"example": "user",
|
||||||
|
})
|
||||||
|
|||||||
39
src/ahriman/web/schemas/logs_search_schema.py
Normal file
39
src/ahriman/web/schemas/logs_search_schema.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#
|
||||||
|
# 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__,
|
||||||
|
})
|
||||||
@@ -37,22 +37,14 @@ class PackagePropertiesSchema(Schema):
|
|||||||
"description": "Package build timestamp",
|
"description": "Package build timestamp",
|
||||||
"example": 1680537091,
|
"example": 1680537091,
|
||||||
})
|
})
|
||||||
depends = fields.List(fields.String(), metadata={
|
|
||||||
"description": "Package dependencies list",
|
|
||||||
"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={
|
check_depends = fields.List(fields.String(), metadata={
|
||||||
"description": "Package test dependencies list",
|
"description": "Package test dependencies list",
|
||||||
"example": ["python-pytest"],
|
"example": ["python-pytest"],
|
||||||
})
|
})
|
||||||
|
depends = fields.List(fields.String(), metadata={
|
||||||
|
"description": "Package dependencies list",
|
||||||
|
"example": ["devtools"],
|
||||||
|
})
|
||||||
description = fields.String(metadata={
|
description = fields.String(metadata={
|
||||||
"description": "Package description",
|
"description": "Package description",
|
||||||
"example": "ArcH linux ReposItory MANager",
|
"example": "ArcH linux ReposItory MANager",
|
||||||
@@ -73,6 +65,14 @@ 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"],
|
||||||
|
|||||||
@@ -32,18 +32,18 @@ class PackageSchema(Schema):
|
|||||||
"description": "Package base",
|
"description": "Package base",
|
||||||
"example": "ahriman",
|
"example": "ahriman",
|
||||||
})
|
})
|
||||||
version = fields.String(required=True, metadata={
|
packager = fields.String(metadata={
|
||||||
"description": "Package version",
|
"description": "packager for the last success package build",
|
||||||
"example": __version__,
|
"example": "ahriman bot <ahriman@example.com>",
|
||||||
})
|
|
||||||
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",
|
||||||
})
|
})
|
||||||
packager = fields.String(metadata={
|
remote = fields.Nested(RemoteSchema(), required=True, metadata={
|
||||||
"description": "packager for the last success package build",
|
"description": "Package remote properties",
|
||||||
"example": "ahriman bot <ahriman@example.com>",
|
})
|
||||||
|
version = fields.String(required=True, metadata={
|
||||||
|
"description": "Package version",
|
||||||
|
"example": __version__,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,19 +25,19 @@ class RepositoryStatsSchema(Schema):
|
|||||||
response repository stats schema
|
response repository stats schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bases = fields.Int(metadata={
|
|
||||||
"description": "Amount of unique packages bases",
|
|
||||||
"example": 2,
|
|
||||||
})
|
|
||||||
packages = fields.Int(metadata={
|
|
||||||
"description": "Amount of unique packages",
|
|
||||||
"example": 4,
|
|
||||||
})
|
|
||||||
archive_size = fields.Int(metadata={
|
archive_size = fields.Int(metadata={
|
||||||
"description": "Total archive size of the packages in bytes",
|
"description": "Total archive size of the packages in bytes",
|
||||||
"example": 42000,
|
"example": 42000,
|
||||||
})
|
})
|
||||||
|
bases = fields.Int(metadata={
|
||||||
|
"description": "Amount of unique packages bases",
|
||||||
|
"example": 2,
|
||||||
|
})
|
||||||
installed_size = fields.Int(metadata={
|
installed_size = fields.Int(metadata={
|
||||||
"description": "Total installed size of the packages in bytes",
|
"description": "Total installed size of the packages in bytes",
|
||||||
"example": 42000000,
|
"example": 42000000,
|
||||||
})
|
})
|
||||||
|
packages = fields.Int(metadata={
|
||||||
|
"description": "Amount of unique packages",
|
||||||
|
"example": 4,
|
||||||
|
})
|
||||||
|
|||||||
@@ -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"],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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
|
||||||
@@ -37,6 +38,10 @@ 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
|
||||||
@@ -66,8 +71,19 @@ 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": [
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
|
from ahriman.core.types import Comparable
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.models.worker import Worker
|
from ahriman.models.worker import Worker
|
||||||
from ahriman.web.apispec.decorators import apidocs
|
from ahriman.web.apispec.decorators import apidocs
|
||||||
@@ -74,7 +75,7 @@ class WorkersView(BaseView):
|
|||||||
"""
|
"""
|
||||||
workers = self.workers.workers
|
workers = self.workers.workers
|
||||||
|
|
||||||
comparator: Callable[[Worker], str] = lambda item: item.identifier
|
comparator: Callable[[Worker], Comparable] = lambda item: item.identifier
|
||||||
response = [worker.view() for worker in sorted(workers, key=comparator)]
|
response = [worker.view() for worker in sorted(workers, key=comparator)]
|
||||||
|
|
||||||
return json_response(response)
|
return json_response(response)
|
||||||
|
|||||||
@@ -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, -1, 0)
|
logs = self.service(package_base=package_base).package_logs_get(package_base, None, None, -1, 0)
|
||||||
except UnknownPackageError:
|
except UnknownPackageError:
|
||||||
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
|
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,12 @@ from ahriman.models.build_status import BuildStatusEnum
|
|||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
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 PackageNameSchema, PackageStatusSchema, PackageStatusSimplifiedSchema, \
|
from ahriman.web.schemas import (
|
||||||
RepositoryIdSchema
|
PackageNameSchema,
|
||||||
|
PackageStatusSchema,
|
||||||
|
PackageStatusSimplifiedSchema,
|
||||||
|
RepositoryIdSchema,
|
||||||
|
)
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from aiohttp.web import HTTPNoContent, Response, json_response
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
|
from ahriman.core.types import Comparable
|
||||||
from ahriman.models.build_status import BuildStatus
|
from ahriman.models.build_status import BuildStatus
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
@@ -68,7 +69,7 @@ class PackagesView(StatusViewGuard, BaseView):
|
|||||||
repository_id = self.repository_id()
|
repository_id = self.repository_id()
|
||||||
packages = self.service(repository_id).packages
|
packages = self.service(repository_id).packages
|
||||||
|
|
||||||
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda items: items[0].base
|
comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda items: items[0].base
|
||||||
response = [
|
response = [
|
||||||
{
|
{
|
||||||
"package": package.view(),
|
"package": package.view(),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user