Compare commits

...

25 Commits

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

* update faq formatting

* fix setup doc

* fix docs according to the generated htmls
2022-05-06 02:54:37 +03:00
304690e0d0 correct archllinux wording in readme 2022-05-06 02:54:37 +03:00
3d9fae5415 add __all__ attribute 2022-05-06 02:54:37 +03:00
b7dca2d797 add note about ahriman to index page 2022-05-06 02:54:37 +03:00
b7debddaea Complete official repository support (#59) 2022-05-03 00:49:32 +03:00
1cfc751d21 rename classes in order to make documentation reference work 2022-04-18 05:30:02 +03:00
6ebbb04504 readthedoc integration 2022-04-18 04:39:40 +03:00
c9ee470ee2 move documentation to tox 2022-04-18 01:48:36 +03:00
a2610504e5 apply data migration in the same transaction block with schema migration 2022-04-18 01:19:38 +03:00
36b8b0f46a fix target naming 2022-04-18 01:19:20 +03:00
d90f417cae Docstring update (#58)
* migrate docstrings from reST to google format

* add raises note

Also change behaviour of the `from_option` method to fallback to
disabled instead of raising exception on unknown option

* fix part of warnings for sphinx

* make identation a bit more readable

* review fixes

* add verbose description for properties to make them parsed by sphinx extenstion

* add demo sphinx generator
2022-04-17 20:25:28 +03:00
0db619136d Release 2.0.0rc7 2022-04-11 00:48:08 +03:00
208a9b920d docs update 2022-04-11 00:46:46 +03:00
cb63bc08ff add backup and restore subcommands 2022-04-10 21:34:34 +03:00
6551c8d983 merge restore to rebuild commannd 2022-04-10 01:51:12 +03:00
a6c8d64053 Release 2.0.0rc6 2022-04-09 17:34:23 +03:00
fd78f2b5e2 do not render failed packages in jinja (#57)
basic templates require package info which is unavailable if package
wasn't built
2022-04-09 17:31:13 +03:00
310 changed files with 13244 additions and 7902 deletions

View File

@ -5,6 +5,7 @@ on:
branches: [ master ]
tags:
- '*'
- '!*rc*'
jobs:
docker-image:

2
.gitignore vendored
View File

@ -97,3 +97,5 @@ ENV/
status_cache.json
*.db
docs/html/

20
.readthedocs.yaml Normal file
View File

@ -0,0 +1,20 @@
version: 2
formats: all
build:
os: ubuntu-20.04
tools:
python: "3.9"
sphinx:
builder: html
configuration: docs/conf.py
python:
install:
- method: pip
path: .
extra_requirements:
- docs
system_packages: true

View File

@ -1,4 +1,4 @@
.PHONY: architecture archive archive_directory archlinux check clean directory man push tests version
.PHONY: archive archive_directory archlinux check clean directory push spec spec-html tests version
.DEFAULT_GOAL := archlinux
PROJECT := ahriman
@ -10,9 +10,6 @@ IGNORE_FILES := package/archlinux src/.mypy_cache
$(TARGET_FILES) : $(addprefix $(PROJECT), %) : $(addprefix ., %) directory version
@cp -rp $< $@
architecture:
cd src && pydeps ahriman -o ../docs/ahriman-architecture.svg --no-show --cluster
archive: archive_directory
tar cJf "$(PROJECT)-$(VERSION)-src.tar.xz" "$(PROJECT)"
rm -rf "$(PROJECT)"
@ -36,16 +33,22 @@ clean:
directory: clean
mkdir "$(PROJECT)"
man:
cd src && PYTHONPATH=. argparse-manpage --module ahriman.application.ahriman --function _parser --author "ahriman team" --project-name ahriman --author-email "" --url https://github.com/arcan1s/ahriman --output ../docs/ahriman.1
push: architecture man archlinux
push: spec archlinux
git add package/archlinux/PKGBUILD src/ahriman/version.py docs/ahriman-architecture.svg docs/ahriman.1
git commit -m "Release $(VERSION)"
git tag "$(VERSION)"
git push
git push --tags
spec:
# make sure that old files are removed
find docs -type f -name "$(PROJECT)*.rst" -delete
tox -e docs
spec-html: spec
rm -rf docs/html
tox -e docs-html
tests: clean
tox -e tests

View File

@ -1,9 +1,10 @@
# ArcH Linux ReposItory MANager
# ArcH linux ReposItory MANager
[![tests status](https://github.com/arcan1s/ahriman/actions/workflows/run-tests.yml/badge.svg)](https://github.com/arcan1s/ahriman/actions/workflows/run-tests.yml)
[![setup status](https://github.com/arcan1s/ahriman/actions/workflows/run-setup.yml/badge.svg)](https://github.com/arcan1s/ahriman/actions/workflows/run-setup.yml)
[![docker image](https://github.com/arcan1s/ahriman/actions/workflows/docker-image.yml/badge.svg)](https://github.com/arcan1s/ahriman/actions/workflows/docker-image.yml)
[![CodeFactor](https://www.codefactor.io/repository/github/arcan1s/ahriman/badge)](https://www.codefactor.io/repository/github/arcan1s/ahriman)
[![Documentation Status](https://readthedocs.org/projects/ahriman/badge/?version=latest)](https://ahriman.readthedocs.io/?badge=latest)
Wrapper for managing custom repository inspired by [repo-scripts](https://github.com/arcan1s/repo-scripts).
@ -13,7 +14,7 @@ Wrapper for managing custom repository inspired by [repo-scripts](https://github
* Multi-architecture support.
* VCS packages support.
* Sign support with gpg (repository, package, per package settings).
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram).
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram) and even ability to write own extensions.
* Dependency manager.
* Ability to patch AUR packages and even create package from local PKGBUILDs.
* Repository status interface with optional authorization and control options:
@ -22,10 +23,10 @@ Wrapper for managing custom repository inspired by [repo-scripts](https://github
## Installation and run
For installation details please refer to the [documentation](docs/setup.md). For command help, `--help` subcommand must be used. Subcommands have own help message as well. The package also provides a [man page](docs/ahriman.1).
For installation details please refer to the [documentation](docs/setup.rst). For command help, `--help` subcommand must be used. Subcommands have own help message as well. The package also provides a [man page](docs/ahriman.1).
## Configuration
Every available option is described in the [documentation](docs/configuration.md).
Every available option is described in the [documentation](docs/configuration.rst).
## [FAQ](docs/faq.md)
## [FAQ](docs/faq.rst)

43
docs/advanced-usage.rst Normal file
View File

@ -0,0 +1,43 @@
Advanced usage
==============
Depending on the goal the package can be used in different ways. Nevertheless, in the most cases you will need some basic classes
.. code-block:: python
from pathlib import Path
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
architecture = "x86_64"
configuration = Configuration.from_path(Path("/etc/ahriman.ini"), architecture, quiet=False)
sqlite = SQLite.load(configuration)
At this point there are ``configuration`` and ``database`` instances which can be used later at any time anywhere, e.g.
.. code-block:: python
# instance of ``RepositoryPaths`` class
paths = configuration.repository_paths
Almost all actions are wrapped by ``ahriman.core.repository.Repository`` class
.. code-block:: python
from ahriman.core.repository import Repository
repository = Repository(architecture, configuration, database, no_report=False, unsafe=False)
And the ``repository`` instance can be used to perform repository maintenance
.. code-block:: python
build_result = repository.process_build(known_packages)
built_packages = repository.packages_built()
update_result = repository.process_update(built_packages)
repository.process_report(None, update_result)
repository.process_sync(None, update_result.success)
For the more info please refer to the classes documentation.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 504 KiB

After

Width:  |  Height:  |  Size: 517 KiB

View File

@ -1,13 +1,13 @@
.TH ahriman "1" Manual
.TH AHRIMAN "1" Manual
.SH NAME
ahriman
.SH SYNOPSIS
.B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-update,update,user-add,user-list,user-remove,web} ...
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-triggers,repo-update,update,user-add,user-list,user-remove,web} ...
.SH DESCRIPTION
ArcH Linux ReposItory MANager
.SH OPTIONS
.SH OPTIONS
.TP
\fB\-a\fR \fI\,ARCHITECTURE\/\fR, \fB\-\-architecture\fR \fI\,ARCHITECTURE\/\fR
target architectures (can be used multiple times)
@ -40,8 +40,8 @@ allow to run ahriman as non\-ahriman user. Some actions might be unavailable
\fB\-v\fR, \fB\-\-version\fR
show program's version number and exit
.SS
\fBSub-commands\fR
.SH
COMMAND
.TP
\fBahriman\fR \fI\,aur-search\/\fR
search for package
@ -79,6 +79,9 @@ list patch sets
\fBahriman\fR \fI\,patch-remove\/\fR
remove patch set
.TP
\fBahriman\fR \fI\,repo-backup\/\fR
backup repository data
.TP
\fBahriman\fR \fI\,repo-check\/\fR
check for updates
.TP
@ -94,11 +97,8 @@ rebuild repository
\fBahriman\fR \fI\,repo-remove-unknown\/\fR
remove unknown packages
.TP
\fBahriman\fR \fI\,repo-report\/\fR
generate report
.TP
\fBahriman\fR \fI\,repo-restore\/\fR
restore repository
restore repository data
.TP
\fBahriman\fR \fI\,repo-setup\/\fR
initial service configuration
@ -109,8 +109,8 @@ sign packages
\fBahriman\fR \fI\,repo-status-update\/\fR
update repository status
.TP
\fBahriman\fR \fI\,repo-sync\/\fR
sync repository
\fBahriman\fR \fI\,repo-triggers\/\fR
run triggers
.TP
\fBahriman\fR \fI\,repo-update\/\fR
update packages
@ -126,9 +126,9 @@ remove user
.TP
\fBahriman\fR \fI\,web\/\fR
web server
.SH OPTIONS 'ahriman aur-search'
.SH COMMAND \fI\,'ahriman aur-search'\/\fR
usage: ahriman aur-search [-h] [-e] [-i]
[--sort-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,url,url_path,version}]
[--sort-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,url,url_path,version}]
search [search ...]
search for package in AUR using API
@ -137,6 +137,7 @@ search for package in AUR using API
\fBsearch\fR
search terms, can be specified multiple times, result will match all terms
.SH OPTIONS \fI\,'ahriman aur-search'\/\fR
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
@ -146,35 +147,11 @@ return non\-zero exit status if result is empty
show additional package information
.TP
\fB\-\-sort\-by\fR {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,url,url_path,version}
\fB\-\-sort\-by\fR \fI\,{description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,url,url_path,version}\/\fR
sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted
by name
.SH OPTIONS 'ahriman search'
usage: ahriman aur-search [-h] [-e] [-i]
[--sort-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,url,url_path,version}]
search [search ...]
search for package in AUR using API
.TP
\fBsearch\fR
search terms, can be specified multiple times, result will match all terms
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-i\fR, \fB\-\-info\fR
show additional package information
.TP
\fB\-\-sort\-by\fR {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,url,url_path,version}
sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted
by name
.SH OPTIONS 'ahriman help'
.SH COMMAND \fI\,'ahriman help'\/\fR
usage: ahriman help [-h] [command]
show help message for application or command and exit
@ -183,19 +160,18 @@ show help message for application or command and exit
\fBcommand\fR
show help message for specific command
.SH OPTIONS 'ahriman help-commands-unsafe'
.SH COMMAND \fI\,'ahriman help-commands-unsafe'\/\fR
usage: ahriman help-commands-unsafe [-h] [--command COMMAND]
list unsafe commands as defined in default args
.SH OPTIONS \fI\,'ahriman help-commands-unsafe'\/\fR
.TP
\fB\-\-command\fR \fI\,COMMAND\/\fR
instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1
otherwise
.SH OPTIONS 'ahriman key-import'
.SH COMMAND \fI\,'ahriman key-import'\/\fR
usage: ahriman key-import [-h] [--key-server KEY_SERVER] key
import PGP key from public sources to the repository user
@ -204,11 +180,12 @@ import PGP key from public sources to the repository user
\fBkey\fR
PGP key to import from public server
.SH OPTIONS \fI\,'ahriman key-import'\/\fR
.TP
\fB\-\-key\-server\fR \fI\,KEY_SERVER\/\fR
key server for key import
.SH OPTIONS 'ahriman package-add'
.SH COMMAND \fI\,'ahriman package-add'\/\fR
usage: ahriman package-add [-h] [-e] [-n]
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}]
[--without-dependencies]
@ -220,6 +197,7 @@ add existing or new package to the build queue
\fBpackage\fR
package source (base name, path to local files, remote URL)
.SH OPTIONS \fI\,'ahriman package-add'\/\fR
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
@ -229,70 +207,14 @@ return non\-zero exit status if result is empty
run update function after
.TP
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}
\fB\-s\fR \fI\,{PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}\/\fR, \fB\-\-source\fR \fI\,{PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}\/\fR
explicitly specify the package source for this command
.TP
\fB\-\-without\-dependencies\fR
do not add dependencies
.SH OPTIONS 'ahriman add'
usage: ahriman package-add [-h] [-e] [-n]
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}]
[--without-dependencies]
package [package ...]
add existing or new package to the build queue
.TP
\fBpackage\fR
package source (base name, path to local files, remote URL)
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-n\fR, \fB\-\-now\fR
run update function after
.TP
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}
explicitly specify the package source for this command
.TP
\fB\-\-without\-dependencies\fR
do not add dependencies
.SH OPTIONS 'ahriman package-update'
usage: ahriman package-add [-h] [-e] [-n]
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}]
[--without-dependencies]
package [package ...]
add existing or new package to the build queue
.TP
\fBpackage\fR
package source (base name, path to local files, remote URL)
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-n\fR, \fB\-\-now\fR
run update function after
.TP
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}
explicitly specify the package source for this command
.TP
\fB\-\-without\-dependencies\fR
do not add dependencies
.SH OPTIONS 'ahriman package-remove'
.SH COMMAND \fI\,'ahriman package-remove'\/\fR
usage: ahriman package-remove [-h] package [package ...]
remove package from the repository
@ -301,18 +223,7 @@ remove package from the repository
\fBpackage\fR
package name or base
.SH OPTIONS 'ahriman remove'
usage: ahriman package-remove [-h] package [package ...]
remove package from the repository
.TP
\fBpackage\fR
package name or base
.SH OPTIONS 'ahriman package-status'
.SH COMMAND \fI\,'ahriman package-status'\/\fR
usage: ahriman package-status [-h] [--ahriman] [-e] [-i]
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
[package ...]
@ -323,6 +234,7 @@ request status of the package
\fBpackage\fR
filter status by package base
.SH OPTIONS \fI\,'ahriman package-status'\/\fR
.TP
\fB\-\-ahriman\fR
get service status itself
@ -336,37 +248,10 @@ return non\-zero exit status if result is empty
show additional package information
.TP
\fB\-s\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}, \fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
\fB\-s\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR, \fB\-\-status\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR
filter packages by status
.SH OPTIONS 'ahriman status'
usage: ahriman package-status [-h] [--ahriman] [-e] [-i]
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
[package ...]
request status of the package
.TP
\fBpackage\fR
filter status by package base
.TP
\fB\-\-ahriman\fR
get service status itself
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-i\fR, \fB\-\-info\fR
show additional package information
.TP
\fB\-s\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}, \fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
filter packages by status
.SH OPTIONS 'ahriman package-status-remove'
.SH COMMAND \fI\,'ahriman package-status-remove'\/\fR
usage: ahriman package-status-remove [-h] package [package ...]
remove the package from the status page
@ -375,8 +260,7 @@ remove the package from the status page
\fBpackage\fR
remove specified packages
.SH OPTIONS 'ahriman package-status-update'
.SH COMMAND \fI\,'ahriman package-status-update'\/\fR
usage: ahriman package-status-update [-h]
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
[package ...]
@ -387,26 +271,12 @@ update package status on the status page
\fBpackage\fR
set status for specified packages. If no packages supplied, service status will be updated
.SH OPTIONS \fI\,'ahriman package-status-update'\/\fR
.TP
\fB\-s\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}, \fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
\fB\-s\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR, \fB\-\-status\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR
new status
.SH OPTIONS 'ahriman status-update'
usage: ahriman package-status-update [-h]
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
[package ...]
update package status on the status page
.TP
\fBpackage\fR
set status for specified packages. If no packages supplied, service status will be updated
.TP
\fB\-s\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}, \fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
new status
.SH OPTIONS 'ahriman patch-add'
.SH COMMAND \fI\,'ahriman patch-add'\/\fR
usage: ahriman patch-add [-h] [-t TRACK] package
create or update source patches
@ -415,11 +285,12 @@ create or update source patches
\fBpackage\fR
path to directory with changed files for patch addition/update
.SH OPTIONS \fI\,'ahriman patch-add'\/\fR
.TP
\fB\-t\fR \fI\,TRACK\/\fR, \fB\-\-track\fR \fI\,TRACK\/\fR
files which has to be tracked
.SH OPTIONS 'ahriman patch-list'
.SH COMMAND \fI\,'ahriman patch-list'\/\fR
usage: ahriman patch-list [-h] [-e] [package]
list available patches for the package
@ -428,11 +299,12 @@ list available patches for the package
\fBpackage\fR
package base
.SH OPTIONS \fI\,'ahriman patch-list'\/\fR
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.SH OPTIONS 'ahriman patch-remove'
.SH COMMAND \fI\,'ahriman patch-remove'\/\fR
usage: ahriman patch-remove [-h] package
remove patches for the package
@ -441,8 +313,16 @@ remove patches for the package
\fBpackage\fR
package base
.SH COMMAND \fI\,'ahriman repo-backup'\/\fR
usage: ahriman repo-backup [-h] path
.SH OPTIONS 'ahriman repo-check'
backup settings and database
.TP
\fBpath\fR
path of the output archive
.SH COMMAND \fI\,'ahriman repo-check'\/\fR
usage: ahriman repo-check [-h] [-e] [--no-vcs] [package ...]
check for packages updates. Same as update \-\-dry\-run \-\-no\-manual
@ -451,6 +331,7 @@ check for packages updates. Same as update \-\-dry\-run \-\-no\-manual
\fBpackage\fR
filter check by package base
.SH OPTIONS \fI\,'ahriman repo-check'\/\fR
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
@ -459,29 +340,12 @@ return non\-zero exit status if result is empty
\fB\-\-no\-vcs\fR
do not check VCS packages
.SH OPTIONS 'ahriman check'
usage: ahriman repo-check [-h] [-e] [--no-vcs] [package ...]
check for packages updates. Same as update \-\-dry\-run \-\-no\-manual
.TP
\fBpackage\fR
filter check by package base
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-\-no\-vcs\fR
do not check VCS packages
.SH OPTIONS 'ahriman repo-clean'
.SH COMMAND \fI\,'ahriman repo-clean'\/\fR
usage: ahriman repo-clean [-h] [--cache] [--chroot] [--manual] [--packages]
remove local caches
.SH OPTIONS \fI\,'ahriman repo-clean'\/\fR
.TP
\fB\-\-cache\fR
clear directory with package caches
@ -498,48 +362,17 @@ clear manually added packages queue
\fB\-\-packages\fR
clear directory with built packages
.SH OPTIONS 'ahriman clean'
usage: ahriman repo-clean [-h] [--cache] [--chroot] [--manual] [--packages]
remove local caches
.TP
\fB\-\-cache\fR
clear directory with package caches
.TP
\fB\-\-chroot\fR
clear build chroot
.TP
\fB\-\-manual\fR
clear manually added packages queue
.TP
\fB\-\-packages\fR
clear directory with built packages
.SH OPTIONS 'ahriman repo-config'
.SH COMMAND \fI\,'ahriman repo-config'\/\fR
usage: ahriman repo-config [-h]
dump configuration for the specified architecture
.SH OPTIONS 'ahriman config'
usage: ahriman repo-config [-h]
dump configuration for the specified architecture
.SH OPTIONS 'ahriman repo-rebuild'
usage: ahriman repo-rebuild [-h] [--depends-on DEPENDS_ON] [--dry-run] [-e]
.SH COMMAND \fI\,'ahriman repo-rebuild'\/\fR
usage: ahriman repo-rebuild [-h] [--depends-on DEPENDS_ON] [--dry-run] [--from-database] [-e]
force rebuild whole repository
.SH OPTIONS \fI\,'ahriman repo-rebuild'\/\fR
.TP
\fB\-\-depends\-on\fR \fI\,DEPENDS_ON\/\fR
only rebuild packages that depend on specified package
@ -549,33 +382,21 @@ only rebuild packages that depend on specified package
just perform check for packages without rebuild process itself
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.SH OPTIONS 'ahriman rebuild'
usage: ahriman repo-rebuild [-h] [--depends-on DEPENDS_ON] [--dry-run] [-e]
force rebuild whole repository
.TP
\fB\-\-depends\-on\fR \fI\,DEPENDS_ON\/\fR
only rebuild packages that depend on specified package
.TP
\fB\-\-dry\-run\fR
just perform check for packages without rebuild process itself
\fB\-\-from\-database\fR
read packages from database instead of filesystem. This feature in particular is required in case if you would like to
restore repository from another repository instance. Note however that in order to restore packages you need to have
original ahriman instance run with web service and have run repo\-update at least once.
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.SH OPTIONS 'ahriman repo-remove-unknown'
.SH COMMAND \fI\,'ahriman repo-remove-unknown'\/\fR
usage: ahriman repo-remove-unknown [-h] [--dry-run] [-i]
remove packages which are missing in AUR and do not have local PKGBUILDs
.SH OPTIONS \fI\,'ahriman repo-remove-unknown'\/\fR
.TP
\fB\-\-dry\-run\fR
just perform check for packages without removal
@ -584,85 +405,30 @@ just perform check for packages without removal
\fB\-i\fR, \fB\-\-info\fR
show additional package information
.SH OPTIONS 'ahriman remove-unknown'
usage: ahriman repo-remove-unknown [-h] [--dry-run] [-i]
remove packages which are missing in AUR and do not have local PKGBUILDs
.SH COMMAND \fI\,'ahriman repo-restore'\/\fR
usage: ahriman repo-restore [-h] [-o OUTPUT] path
restore settings and database
.TP
\fB\-\-dry\-run\fR
just perform check for packages without removal
\fBpath\fR
path of the input archive
.SH OPTIONS \fI\,'ahriman repo-restore'\/\fR
.TP
\fB\-i\fR, \fB\-\-info\fR
show additional package information
\fB\-o\fR \fI\,OUTPUT\/\fR, \fB\-\-output\fR \fI\,OUTPUT\/\fR
root path of the extracted files
.SH OPTIONS 'ahriman repo-report'
usage: ahriman repo-report [-h] [target ...]
generate repository report according to current settings
.TP
\fBtarget\fR
target to generate report
.SH OPTIONS 'ahriman report'
usage: ahriman repo-report [-h] [target ...]
generate repository report according to current settings
.TP
\fBtarget\fR
target to generate report
.SH OPTIONS 'ahriman repo-restore'
usage: ahriman repo-restore [-h] [-e] [-n] [--without-dependencies]
restore repository from database file
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-n\fR, \fB\-\-now\fR
run update function after
.TP
\fB\-\-without\-dependencies\fR
do not add dependencies
.SH OPTIONS 'ahriman restore'
usage: ahriman repo-restore [-h] [-e] [-n] [--without-dependencies]
restore repository from database file
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-n\fR, \fB\-\-now\fR
run update function after
.TP
\fB\-\-without\-dependencies\fR
do not add dependencies
.SH OPTIONS 'ahriman repo-setup'
.SH COMMAND \fI\,'ahriman repo-setup'\/\fR
usage: ahriman repo-setup [-h] [--build-as-user BUILD_AS_USER] [--build-command BUILD_COMMAND]
[--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository
REPOSITORY [--sign-key SIGN_KEY]
[--sign-target {SignSettings.Packages,SignSettings.Repository}] [--web-port WEB_PORT]
[--sign-target {SignSettings.Disabled,SignSettings.Packages,SignSettings.Repository}]
[--web-port WEB_PORT]
create initial service configuration, requires root
.SH OPTIONS \fI\,'ahriman repo-setup'\/\fR
.TP
\fB\-\-build\-as\-user\fR \fI\,BUILD_AS_USER\/\fR
force makepkg user to the specific one
@ -692,149 +458,14 @@ repository name
sign key id
.TP
\fB\-\-sign\-target\fR {SignSettings.Packages,SignSettings.Repository}
\fB\-\-sign\-target\fR \fI\,{SignSettings.Disabled,SignSettings.Packages,SignSettings.Repository}\/\fR
sign options
.TP
\fB\-\-web\-port\fR \fI\,WEB_PORT\/\fR
port of the web service
.SH OPTIONS 'ahriman init'
usage: ahriman repo-setup [-h] [--build-as-user BUILD_AS_USER] [--build-command BUILD_COMMAND]
[--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository
REPOSITORY [--sign-key SIGN_KEY]
[--sign-target {SignSettings.Packages,SignSettings.Repository}] [--web-port WEB_PORT]
create initial service configuration, requires root
.TP
\fB\-\-build\-as\-user\fR \fI\,BUILD_AS_USER\/\fR
force makepkg user to the specific one
.TP
\fB\-\-build\-command\fR \fI\,BUILD_COMMAND\/\fR
build command prefix
.TP
\fB\-\-from\-configuration\fR \fI\,FROM_CONFIGURATION\/\fR
path to default devtools pacman configuration
.TP
\fB\-\-no\-multilib\fR
do not add multilib repository
.TP
\fB\-\-packager\fR \fI\,PACKAGER\/\fR
packager name and email
.TP
\fB\-\-repository\fR \fI\,REPOSITORY\/\fR
repository name
.TP
\fB\-\-sign\-key\fR \fI\,SIGN_KEY\/\fR
sign key id
.TP
\fB\-\-sign\-target\fR {SignSettings.Packages,SignSettings.Repository}
sign options
.TP
\fB\-\-web\-port\fR \fI\,WEB_PORT\/\fR
port of the web service
.SH OPTIONS 'ahriman repo-init'
usage: ahriman repo-setup [-h] [--build-as-user BUILD_AS_USER] [--build-command BUILD_COMMAND]
[--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository
REPOSITORY [--sign-key SIGN_KEY]
[--sign-target {SignSettings.Packages,SignSettings.Repository}] [--web-port WEB_PORT]
create initial service configuration, requires root
.TP
\fB\-\-build\-as\-user\fR \fI\,BUILD_AS_USER\/\fR
force makepkg user to the specific one
.TP
\fB\-\-build\-command\fR \fI\,BUILD_COMMAND\/\fR
build command prefix
.TP
\fB\-\-from\-configuration\fR \fI\,FROM_CONFIGURATION\/\fR
path to default devtools pacman configuration
.TP
\fB\-\-no\-multilib\fR
do not add multilib repository
.TP
\fB\-\-packager\fR \fI\,PACKAGER\/\fR
packager name and email
.TP
\fB\-\-repository\fR \fI\,REPOSITORY\/\fR
repository name
.TP
\fB\-\-sign\-key\fR \fI\,SIGN_KEY\/\fR
sign key id
.TP
\fB\-\-sign\-target\fR {SignSettings.Packages,SignSettings.Repository}
sign options
.TP
\fB\-\-web\-port\fR \fI\,WEB_PORT\/\fR
port of the web service
.SH OPTIONS 'ahriman setup'
usage: ahriman repo-setup [-h] [--build-as-user BUILD_AS_USER] [--build-command BUILD_COMMAND]
[--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository
REPOSITORY [--sign-key SIGN_KEY]
[--sign-target {SignSettings.Packages,SignSettings.Repository}] [--web-port WEB_PORT]
create initial service configuration, requires root
.TP
\fB\-\-build\-as\-user\fR \fI\,BUILD_AS_USER\/\fR
force makepkg user to the specific one
.TP
\fB\-\-build\-command\fR \fI\,BUILD_COMMAND\/\fR
build command prefix
.TP
\fB\-\-from\-configuration\fR \fI\,FROM_CONFIGURATION\/\fR
path to default devtools pacman configuration
.TP
\fB\-\-no\-multilib\fR
do not add multilib repository
.TP
\fB\-\-packager\fR \fI\,PACKAGER\/\fR
packager name and email
.TP
\fB\-\-repository\fR \fI\,REPOSITORY\/\fR
repository name
.TP
\fB\-\-sign\-key\fR \fI\,SIGN_KEY\/\fR
sign key id
.TP
\fB\-\-sign\-target\fR {SignSettings.Packages,SignSettings.Repository}
sign options
.TP
\fB\-\-web\-port\fR \fI\,WEB_PORT\/\fR
port of the web service
.SH OPTIONS 'ahriman repo-sign'
.SH COMMAND \fI\,'ahriman repo-sign'\/\fR
usage: ahriman repo-sign [-h] [package ...]
(re\-)sign packages and repository database according to current settings
@ -843,49 +474,23 @@ usage: ahriman repo-sign [-h] [package ...]
\fBpackage\fR
sign only specified packages
.SH OPTIONS 'ahriman sign'
usage: ahriman repo-sign [-h] [package ...]
(re\-)sign packages and repository database according to current settings
.TP
\fBpackage\fR
sign only specified packages
.SH OPTIONS 'ahriman repo-status-update'
.SH COMMAND \fI\,'ahriman repo-status-update'\/\fR
usage: ahriman repo-status-update [-h]
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
update repository status on the status page
.SH OPTIONS \fI\,'ahriman repo-status-update'\/\fR
.TP
\fB\-s\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}, \fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
\fB\-s\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR, \fB\-\-status\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR
new status
.SH OPTIONS 'ahriman repo-sync'
usage: ahriman repo-sync [-h] [target ...]
.SH COMMAND \fI\,'ahriman repo-triggers'\/\fR
usage: ahriman repo-triggers [-h]
sync repository files to remote server according to current settings
run triggers on empty build result as configured by settings
.TP
\fBtarget\fR
target to sync
.SH OPTIONS 'ahriman sync'
usage: ahriman repo-sync [-h] [target ...]
sync repository files to remote server according to current settings
.TP
\fBtarget\fR
target to sync
.SH OPTIONS 'ahriman repo-update'
.SH COMMAND \fI\,'ahriman repo-update'\/\fR
usage: ahriman repo-update [-h] [--dry-run] [-e] [--no-aur] [--no-local] [--no-manual] [--no-vcs] [package ...]
check for packages updates and run build process if requested
@ -894,6 +499,7 @@ check for packages updates and run build process if requested
\fBpackage\fR
filter check by package base
.SH OPTIONS \fI\,'ahriman repo-update'\/\fR
.TP
\fB\-\-dry\-run\fR
just perform check for updates, same as check command
@ -918,40 +524,7 @@ do not include manual updates
\fB\-\-no\-vcs\fR
do not check VCS packages
.SH OPTIONS 'ahriman update'
usage: ahriman repo-update [-h] [--dry-run] [-e] [--no-aur] [--no-local] [--no-manual] [--no-vcs] [package ...]
check for packages updates and run build process if requested
.TP
\fBpackage\fR
filter check by package base
.TP
\fB\-\-dry\-run\fR
just perform check for updates, same as check command
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-\-no\-aur\fR
do not check for AUR updates. Implies \-\-no\-vcs
.TP
\fB\-\-no\-local\fR
do not check local packages for updates
.TP
\fB\-\-no\-manual\fR
do not include manual updates
.TP
\fB\-\-no\-vcs\fR
do not check VCS packages
.SH OPTIONS 'ahriman user-add'
.SH COMMAND \fI\,'ahriman user-add'\/\fR
usage: ahriman user-add [-h] [--as-service] [-p PASSWORD] [-r {UserAccess.Safe,UserAccess.Read,UserAccess.Write}] [-s]
username
@ -961,6 +534,7 @@ update user for web services with the given password and role. In case if passwo
\fBusername\fR
username for web service
.SH OPTIONS \fI\,'ahriman user-add'\/\fR
.TP
\fB\-\-as\-service\fR
add user as service user
@ -971,14 +545,14 @@ user password. Blank password will be treated as empty password, which is in par
authorization type.
.TP
\fB\-r\fR {UserAccess.Safe,UserAccess.Read,UserAccess.Write}, \fB\-\-role\fR {UserAccess.Safe,UserAccess.Read,UserAccess.Write}
\fB\-r\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR, \fB\-\-role\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR
user access level
.TP
\fB\-s\fR, \fB\-\-secure\fR
set file permissions to user\-only
.SH OPTIONS 'ahriman user-list'
.SH COMMAND \fI\,'ahriman user-list'\/\fR
usage: ahriman user-list [-h] [-e] [-r {UserAccess.Safe,UserAccess.Read,UserAccess.Write}] [username]
list users from the user mapping and their roles
@ -987,15 +561,16 @@ list users from the user mapping and their roles
\fBusername\fR
filter users by username
.SH OPTIONS \fI\,'ahriman user-list'\/\fR
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.TP
\fB\-r\fR {UserAccess.Safe,UserAccess.Read,UserAccess.Write}, \fB\-\-role\fR {UserAccess.Safe,UserAccess.Read,UserAccess.Write}
\fB\-r\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR, \fB\-\-role\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR
filter users by role
.SH OPTIONS 'ahriman user-remove'
.SH COMMAND \fI\,'ahriman user-remove'\/\fR
usage: ahriman user-remove [-h] [-s] username
remove user from the user mapping and update the configuration
@ -1004,23 +579,26 @@ remove user from the user mapping and update the configuration
\fBusername\fR
username for web service
.SH OPTIONS \fI\,'ahriman user-remove'\/\fR
.TP
\fB\-s\fR, \fB\-\-secure\fR
set file permissions to user\-only
.SH OPTIONS 'ahriman web'
.SH COMMAND \fI\,'ahriman web'\/\fR
usage: ahriman web [-h]
start web server
.SH COMMENTS
Argument list can also be read from file by using @ prefix.
.SH AUTHORS
.B ahriman
was written by ahriman team <>.
.nf
ahriman team
.fi.nf
.fi
.SH DISTRIBUTION
The latest version of ahriman may be downloaded from
.UR https://github.com/arcan1s/ahriman

View File

@ -0,0 +1,45 @@
ahriman.application.application package
=======================================
Submodules
----------
ahriman.application.application.application module
--------------------------------------------------
.. automodule:: ahriman.application.application.application
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.application.application\_packages module
------------------------------------------------------------
.. automodule:: ahriman.application.application.application_packages
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.application.application\_properties module
--------------------------------------------------------------
.. automodule:: ahriman.application.application.application_properties
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.application.application\_repository module
--------------------------------------------------------------
.. automodule:: ahriman.application.application.application_repository
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.application.application
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,189 @@
ahriman.application.handlers package
====================================
Submodules
----------
ahriman.application.handlers.add module
---------------------------------------
.. automodule:: ahriman.application.handlers.add
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.backup module
------------------------------------------
.. automodule:: ahriman.application.handlers.backup
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.clean module
-----------------------------------------
.. automodule:: ahriman.application.handlers.clean
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.dump module
----------------------------------------
.. automodule:: ahriman.application.handlers.dump
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.handler module
-------------------------------------------
.. automodule:: ahriman.application.handlers.handler
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.help module
----------------------------------------
.. automodule:: ahriman.application.handlers.help
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.key\_import module
-----------------------------------------------
.. automodule:: ahriman.application.handlers.key_import
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.patch module
-----------------------------------------
.. automodule:: ahriman.application.handlers.patch
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.rebuild module
-------------------------------------------
.. automodule:: ahriman.application.handlers.rebuild
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.remove module
------------------------------------------
.. automodule:: ahriman.application.handlers.remove
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.remove\_unknown module
---------------------------------------------------
.. automodule:: ahriman.application.handlers.remove_unknown
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.restore module
-------------------------------------------
.. automodule:: ahriman.application.handlers.restore
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.search module
------------------------------------------
.. automodule:: ahriman.application.handlers.search
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.setup module
-----------------------------------------
.. automodule:: ahriman.application.handlers.setup
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.sign module
----------------------------------------
.. automodule:: ahriman.application.handlers.sign
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.status module
------------------------------------------
.. automodule:: ahriman.application.handlers.status
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.status\_update module
--------------------------------------------------
.. automodule:: ahriman.application.handlers.status_update
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.triggers module
--------------------------------------------
.. automodule:: ahriman.application.handlers.triggers
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.unsafe\_commands module
----------------------------------------------------
.. automodule:: ahriman.application.handlers.unsafe_commands
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.update module
------------------------------------------
.. automodule:: ahriman.application.handlers.update
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.users module
-----------------------------------------
.. automodule:: ahriman.application.handlers.users
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.web module
---------------------------------------
.. automodule:: ahriman.application.handlers.web
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.application.handlers
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,38 @@
ahriman.application package
===========================
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.application.application
ahriman.application.handlers
Submodules
----------
ahriman.application.ahriman module
----------------------------------
.. automodule:: ahriman.application.ahriman
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.lock module
-------------------------------
.. automodule:: ahriman.application.lock
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.application
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,45 @@
ahriman.core.alpm.remote package
================================
Submodules
----------
ahriman.core.alpm.remote.aur module
-----------------------------------
.. automodule:: ahriman.core.alpm.remote.aur
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.alpm.remote.official module
----------------------------------------
.. automodule:: ahriman.core.alpm.remote.official
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.alpm.remote.official\_syncdb module
------------------------------------------------
.. automodule:: ahriman.core.alpm.remote.official_syncdb
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.alpm.remote.remote module
--------------------------------------
.. automodule:: ahriman.core.alpm.remote.remote
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.alpm.remote
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,37 @@
ahriman.core.alpm package
=========================
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.core.alpm.remote
Submodules
----------
ahriman.core.alpm.pacman module
-------------------------------
.. automodule:: ahriman.core.alpm.pacman
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.alpm.repo module
-----------------------------
.. automodule:: ahriman.core.alpm.repo
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.alpm
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,45 @@
ahriman.core.auth package
=========================
Submodules
----------
ahriman.core.auth.auth module
-----------------------------
.. automodule:: ahriman.core.auth.auth
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.auth.helpers module
--------------------------------
.. automodule:: ahriman.core.auth.helpers
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.auth.mapping module
--------------------------------
.. automodule:: ahriman.core.auth.mapping
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.auth.oauth module
------------------------------
.. automodule:: ahriman.core.auth.oauth
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.auth
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,29 @@
ahriman.core.build\_tools package
=================================
Submodules
----------
ahriman.core.build\_tools.sources module
----------------------------------------
.. automodule:: ahriman.core.build_tools.sources
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.build\_tools.task module
-------------------------------------
.. automodule:: ahriman.core.build_tools.task
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.build_tools
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,45 @@
ahriman.core.database.data package
==================================
Submodules
----------
ahriman.core.database.data.package\_remotes module
--------------------------------------------------
.. automodule:: ahriman.core.database.data.package_remotes
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.data.package\_statuses module
---------------------------------------------------
.. automodule:: ahriman.core.database.data.package_statuses
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.data.patches module
-----------------------------------------
.. automodule:: ahriman.core.database.data.patches
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.data.users module
---------------------------------------
.. automodule:: ahriman.core.database.data.users
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.database.data
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,29 @@
ahriman.core.database.migrations package
========================================
Submodules
----------
ahriman.core.database.migrations.m000\_initial module
-----------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m000_initial
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.migrations.m001\_package\_source module
-------------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m001_package_source
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.database.migrations
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,53 @@
ahriman.core.database.operations package
========================================
Submodules
----------
ahriman.core.database.operations.auth\_operations module
--------------------------------------------------------
.. automodule:: ahriman.core.database.operations.auth_operations
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.operations.build\_operations module
---------------------------------------------------------
.. automodule:: ahriman.core.database.operations.build_operations
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.operations.operations module
--------------------------------------------------
.. automodule:: ahriman.core.database.operations.operations
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.operations.package\_operations module
-----------------------------------------------------------
.. automodule:: ahriman.core.database.operations.package_operations
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.operations.patch\_operations module
---------------------------------------------------------
.. automodule:: ahriman.core.database.operations.patch_operations
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.database.operations
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,31 @@
ahriman.core.database package
=============================
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.core.database.data
ahriman.core.database.migrations
ahriman.core.database.operations
Submodules
----------
ahriman.core.database.sqlite module
-----------------------------------
.. automodule:: ahriman.core.database.sqlite
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.database
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,85 @@
ahriman.core.formatters package
===============================
Submodules
----------
ahriman.core.formatters.aur\_printer module
-------------------------------------------
.. automodule:: ahriman.core.formatters.aur_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.build\_printer module
---------------------------------------------
.. automodule:: ahriman.core.formatters.build_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.configuration\_printer module
-----------------------------------------------------
.. automodule:: ahriman.core.formatters.configuration_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.package\_printer module
-----------------------------------------------
.. automodule:: ahriman.core.formatters.package_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.printer module
--------------------------------------
.. automodule:: ahriman.core.formatters.printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.status\_printer module
----------------------------------------------
.. automodule:: ahriman.core.formatters.status_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.string\_printer module
----------------------------------------------
.. automodule:: ahriman.core.formatters.string_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.update\_printer module
----------------------------------------------
.. automodule:: ahriman.core.formatters.update_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.user\_printer module
--------------------------------------------
.. automodule:: ahriman.core.formatters.user_printer
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.formatters
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,69 @@
ahriman.core.report package
===========================
Submodules
----------
ahriman.core.report.console module
----------------------------------
.. automodule:: ahriman.core.report.console
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.report.email module
--------------------------------
.. automodule:: ahriman.core.report.email
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.report.html module
-------------------------------
.. automodule:: ahriman.core.report.html
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.report.jinja\_template module
------------------------------------------
.. automodule:: ahriman.core.report.jinja_template
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.report.report module
---------------------------------
.. automodule:: ahriman.core.report.report
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.report.report\_trigger module
------------------------------------------
.. automodule:: ahriman.core.report.report_trigger
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.report.telegram module
-----------------------------------
.. automodule:: ahriman.core.report.telegram
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.report
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,53 @@
ahriman.core.repository package
===============================
Submodules
----------
ahriman.core.repository.cleaner module
--------------------------------------
.. automodule:: ahriman.core.repository.cleaner
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.repository.executor module
---------------------------------------
.. automodule:: ahriman.core.repository.executor
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.repository.repository module
-----------------------------------------
.. automodule:: ahriman.core.repository.repository
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.repository.repository\_properties module
-----------------------------------------------------
.. automodule:: ahriman.core.repository.repository_properties
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.repository.update\_handler module
----------------------------------------------
.. automodule:: ahriman.core.repository.update_handler
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.repository
:members:
:no-undoc-members:
:show-inheritance:

71
docs/ahriman.core.rst Normal file
View File

@ -0,0 +1,71 @@
ahriman.core package
====================
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.core.alpm
ahriman.core.auth
ahriman.core.build_tools
ahriman.core.database
ahriman.core.formatters
ahriman.core.report
ahriman.core.repository
ahriman.core.sign
ahriman.core.status
ahriman.core.triggers
ahriman.core.upload
Submodules
----------
ahriman.core.configuration module
---------------------------------
.. automodule:: ahriman.core.configuration
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.exceptions module
------------------------------
.. automodule:: ahriman.core.exceptions
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.spawn module
-------------------------
.. automodule:: ahriman.core.spawn
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.tree module
------------------------
.. automodule:: ahriman.core.tree
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.util module
------------------------
.. automodule:: ahriman.core.util
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,21 @@
ahriman.core.sign package
=========================
Submodules
----------
ahriman.core.sign.gpg module
----------------------------
.. automodule:: ahriman.core.sign.gpg
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.sign
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,37 @@
ahriman.core.status package
===========================
Submodules
----------
ahriman.core.status.client module
---------------------------------
.. automodule:: ahriman.core.status.client
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.status.watcher module
----------------------------------
.. automodule:: ahriman.core.status.watcher
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.status.web\_client module
--------------------------------------
.. automodule:: ahriman.core.status.web_client
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.status
:members:
:no-undoc-members:
:show-inheritance:

View File

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

View File

@ -0,0 +1,61 @@
ahriman.core.upload package
===========================
Submodules
----------
ahriman.core.upload.github module
---------------------------------
.. automodule:: ahriman.core.upload.github
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.upload.http\_upload module
---------------------------------------
.. automodule:: ahriman.core.upload.http_upload
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.upload.rsync module
--------------------------------
.. automodule:: ahriman.core.upload.rsync
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.upload.s3 module
-----------------------------
.. automodule:: ahriman.core.upload.s3
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.upload.upload module
---------------------------------
.. automodule:: ahriman.core.upload.upload
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.upload.upload\_trigger module
------------------------------------------
.. automodule:: ahriman.core.upload.upload_trigger
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.upload
:members:
:no-undoc-members:
:show-inheritance:

189
docs/ahriman.models.rst Normal file
View File

@ -0,0 +1,189 @@
ahriman.models package
======================
Submodules
----------
ahriman.models.action module
----------------------------
.. automodule:: ahriman.models.action
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.aur\_package module
----------------------------------
.. automodule:: ahriman.models.aur_package
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.auth\_settings module
------------------------------------
.. automodule:: ahriman.models.auth_settings
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.build\_status module
-----------------------------------
.. automodule:: ahriman.models.build_status
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.counters module
------------------------------
.. automodule:: ahriman.models.counters
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.internal\_status module
--------------------------------------
.. automodule:: ahriman.models.internal_status
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.migration module
-------------------------------
.. automodule:: ahriman.models.migration
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.migration\_result module
---------------------------------------
.. automodule:: ahriman.models.migration_result
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.package module
-----------------------------
.. automodule:: ahriman.models.package
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.package\_description module
------------------------------------------
.. automodule:: ahriman.models.package_description
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.package\_source module
-------------------------------------
.. automodule:: ahriman.models.package_source
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.property module
------------------------------
.. automodule:: ahriman.models.property
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.remote\_source module
------------------------------------
.. automodule:: ahriman.models.remote_source
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.report\_settings module
--------------------------------------
.. automodule:: ahriman.models.report_settings
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.repository\_paths module
---------------------------------------
.. automodule:: ahriman.models.repository_paths
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.result module
----------------------------
.. automodule:: ahriman.models.result
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.sign\_settings module
------------------------------------
.. automodule:: ahriman.models.sign_settings
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.smtp\_ssl\_settings module
-----------------------------------------
.. automodule:: ahriman.models.smtp_ssl_settings
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.upload\_settings module
--------------------------------------
.. automodule:: ahriman.models.upload_settings
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.user module
--------------------------
.. automodule:: ahriman.models.user
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.user\_access module
----------------------------------
.. automodule:: ahriman.models.user_access
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.user\_identity module
------------------------------------
.. automodule:: ahriman.models.user_identity
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.models
:members:
:no-undoc-members:
:show-inheritance:

32
docs/ahriman.rst Normal file
View File

@ -0,0 +1,32 @@
ahriman package
===============
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.application
ahriman.core
ahriman.models
ahriman.web
Submodules
----------
ahriman.version module
----------------------
.. automodule:: ahriman.version
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,29 @@
ahriman.web.middlewares package
===============================
Submodules
----------
ahriman.web.middlewares.auth\_handler module
--------------------------------------------
.. automodule:: ahriman.web.middlewares.auth_handler
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.middlewares.exception\_handler module
-------------------------------------------------
.. automodule:: ahriman.web.middlewares.exception_handler
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.middlewares
:members:
:no-undoc-members:
:show-inheritance:

38
docs/ahriman.web.rst Normal file
View File

@ -0,0 +1,38 @@
ahriman.web package
===================
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.web.middlewares
ahriman.web.views
Submodules
----------
ahriman.web.routes module
-------------------------
.. automodule:: ahriman.web.routes
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.web module
----------------------
.. automodule:: ahriman.web.web
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,39 @@
ahriman.web.views package
=========================
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.web.views.service
ahriman.web.views.status
ahriman.web.views.user
Submodules
----------
ahriman.web.views.base module
-----------------------------
.. automodule:: ahriman.web.views.base
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.index module
------------------------------
.. automodule:: ahriman.web.views.index
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,45 @@
ahriman.web.views.service package
=================================
Submodules
----------
ahriman.web.views.service.add module
------------------------------------
.. automodule:: ahriman.web.views.service.add
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.service.remove module
---------------------------------------
.. automodule:: ahriman.web.views.service.remove
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.service.request module
----------------------------------------
.. automodule:: ahriman.web.views.service.request
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.service.search module
---------------------------------------
.. automodule:: ahriman.web.views.service.search
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.service
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,45 @@
ahriman.web.views.status package
================================
Submodules
----------
ahriman.web.views.status.ahriman module
---------------------------------------
.. automodule:: ahriman.web.views.status.ahriman
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.status.package module
---------------------------------------
.. automodule:: ahriman.web.views.status.package
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.status.packages module
----------------------------------------
.. automodule:: ahriman.web.views.status.packages
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.status.status module
--------------------------------------
.. automodule:: ahriman.web.views.status.status
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.status
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,29 @@
ahriman.web.views.user package
==============================
Submodules
----------
ahriman.web.views.user.login module
-----------------------------------
.. automodule:: ahriman.web.views.user.login
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.user.logout module
------------------------------------
.. automodule:: ahriman.web.views.user.logout
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.user
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -1,227 +0,0 @@
# Package structure
Packages have strict rules of importing:
* `ahriman.application` package must not be used anywhere except for itself.
* `ahriman.core` and `ahriman.models` packages don't have any import restriction. Actually we would like to totally restrict importing of `core` package from `models`, but it is impossible at the moment.
* `ahriman.web` package is allowed to be imported from `ahriman.application` (web handler only, only `ahriman.web.web` methods). It also must not be imported globally, only local import is allowed.
Full dependency diagram:
![architecture](ahriman-architecture.svg)
## `ahriman.application` package
This package contains application (aka executable) related classes and everything for that. It also contains package called `ahriman.application.handlers` in which all available subcommands are described as separated classes derived from base `ahriman.application.handlers.handler.Handler` class.
`ahriman.application.application.application.Application` (god class) is used for any interaction from parsers with repository, web etc. It is divided into multiple traits by functions (package related and repository related) in the same package.
`ahriman.application.ahriman` contains only command line parses and executes specified `Handler` on success, `ahriman.application.lock.Lock` is additional class which provides file-based lock and also performs some common checks.
## `ahriman.core` package
This package contains everything which is required for any time of application run and separated to several packages:
* `ahriman.core.alpm` package controls pacman related functions. It provides wrappers for `pyalpm` library and safe calls for repository tools (`repo-add` and `repo-remove`).
* `ahriman.core.auth` package provides classes for authorization methods used by web mostly. Base class is `ahriman.core.auth.auth.Auth` which must be called by `load` method.
* `ahriman.core.build_tools` is a package which provides wrapper for `devtools` commands.
* `ahriman.core.database` is everything including data and schema migrations for database.
* `ahriman.core.formatters` package provides `Printer` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
* `ahriman.core.report` is a package with reporting classes. Usually it must be called by `ahriman.core.report.report.Report.load` method.
* `ahriman.core.repository` contains several traits and base repository (`ahriman.core.repository.repository.Repository` class) implementation.
* `ahriman.core.sign` package provides sign feature (only gpg calls are available).
* `ahriman.core.status` contains helpers and watcher class which are required for web application. Reporter must be initialized by using `ahriman.core.status.client.Client.load` method.
* `ahriman.core.upload` package provides sync feature, must be called by `ahriman.core.upload.upload.Upload.load` method.
This package also provides some generic functions and classes which may be used by other packages:
* `ahriman.core.configuration.Configuration` is an extension for standard `configparser` library.
* `ahriman.core.exceptions` provides custom exceptions.
* `ahriman.core.spawn.Spawn` is a tool which can spawn another `ahriman` process. This feature is used by web application.
* `ahriman.core.tree` is a dependency tree implementation.
## `ahriman.models` package
It provides models for any other part of application. Unlike `ahriman.core` package classes from here provides only conversion methods (e.g. create class from another or convert to). Mostly case classes and enumerations.
## `ahriman.web` package
Web application. It is important that this package is isolated from any other to allow it to be optional feature (i.e. dependencies which are required by the package are optional).
* `ahriman.web.middlewares` provides middlewares for request handlers.
* `ahriman.web.views` contains web views derived from aiohttp view class.
* `ahriman.web.routes` creates routes for web application.
* `ahriman.web.web` provides main web application functions (e.g. start, initialization).
# Application run
* Parse command line arguments, find command and related handler which is set by parser.
* Call `Handler.execute` method.
* Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or process in current process otherwise. Class attribute `ALLOW_MULTI_ARCHITECTURE_RUN` controls whether application can be run in multiple processes or not - this feature is required for some handlers (e.g. `Web`) which should be able to spawn child process in daemon mode (it is impossible to do for daemonic processes).
* In each child process call lock functions.
* After success checks pass control to `Handler.run` method defined by specific handler class.
* Return result (success or failure) of each subprocess and exit from application.
* Some handlers may override their status and throw `ExitCode` exception. This exception is just silently suppressed and changes application exit code to `1`.
In most cases handlers spawn god class `ahriman.application.application.Application` class and call required methods.
Application is designed to run from `systemd` services and provides parametrized by architecture timer and service file for that.
# Database
The service uses SQLite database in order to store some internal info.
## Database instance
All methods related to specific part of database (basically operations per table) are split into different traits located inside `ahriman.core.database.operations` package. The base trait `ahriman.core.database.operations.operations.Operations` also provides generic methods for database access (e.g. row converters and transactional support).
The `ahriman.core.database.sqlite.SQLite` class itself derives from all of these traits and implements methods for initialization, including migrations.
## Schema and data migrations
The schema migration are applied according to current `pragma user_info` values, located at `ahriman.core.database.migrations` package and named as `m000_migration_name.py` (the preceding `m` is required in order to import migration content for tests). Additional class `ahriman.core.database.migrations.Migrations` reads all migrations autmatically and applies them in alphabetical order.
There are also data migrations which are located at `ahriman.core.database.data` package and move data from old-style (e.g. json files in filesystem, directory trees, etc) to the database. They are also part of migration and (unlike schema migrations) are applied only at specific version breakpoints (e.g. if `user_version` is more than 0 no initial migration will be applied).
## Type conversions
By default, it parses rows into python dictionary. In addition, the following pseudo-types are supported:
* `Dict[str, Any]`, `List[Any]` - for storing JSON data structures in database (technically there is no restriction on types for dictionary keys and values, but it is recommended to use only string keys). The type is stored as `json` datatype and `json.loads` and `json.dumps` methods are used in order to read and write from/to database respectively.
# Basic flows
## Add new packages or rebuild existing
Idea is to copy package to the directory from which it will be handled at the next update run. Different variants are supported:
* If supplied argument is file then application moves the file to the directory with built packages. Same rule applies for directory, but in this case it copies every package-like file from the specified directory.
* If supplied argument is directory and there is `PKGBUILD` file there it will be treated as local package. In this case it will queue this package to build and copy source files (`PKGBUILD` and `.SRCINFO`) to caches.
* If supplied argument iis not file then application tries to lookup for the specified name in AUR and clones it into the directory with manual updates. This scenario can also handle package dependencies which are missing in repositories.
This logic can be overwritten by specifying the `source` parameter, which is partially useful if you would like to add package from AUR, but there is local directory cloned from AUR.
## Rebuild packages
Same as add function for every package in repository. Optional filter by reverse dependency can be supplied.
## Remove packages
This flow removes package from filesystem, updates repository database and also runs synchronization and reporting methods.
## Update packages
This feature is divided into to stages: check AUR for updates and run rebuild for required packages. Whereas check does not do anything except for check itself, update flow is the following:
1. Process every built package first. Those packages are usually added manually.
2. Run sync and report methods.
3. Generate dependency tree for packages to be built.
4. For each level of tree it does:
1. Download package data from AUR.
2. Build every package in clean chroot.
3. Sign packages if required.
4. Add packages to database and sign database if required.
5. Process sync and report methods.
After any step any package data is being removed.
# Core functions reference
## Configuration
`ahriman.core.configuration.Configuration` class provides some additional methods (e.g. `getpath` and `getlist`) and also combines multiple files into single configuration dictionary using architecture overrides. It is the recommended way to deal with settings.
## Utils
For every external command run (which is actually not recommended if possible) custom wrapper for `subprocess` is used. Additional functions `ahriman.core.auth.helpers` provide safe calls for `aiohttp_security` methods and are required to make this dependency optional.
## Submodules
Some packages provide different behaviour depending on configuration settings. In these cases inheritance is used and recommended way to deal with them is to call class method `load` from base classes.
## Authorization
The package provides several authorization methods: disabled, based on configuration and OAuth2.
Disabled (default) authorization provider just allows everything for everyone and does not have any specific configuration (it uses some default configuration parameters though). It also provides generic interface for derived classes.
Mapping (aka configuration) provider uses hashed passwords with salt from the database in order to authenticate users. This provider also enables user permission checking (read/write) (authorization). Thus, it defines the following methods:
* `check_credentials` - user password validation (authentication).
* `verify_access` - user permission validation (authorization).
Passwords must be stored in database as `hash(password + salt)`, where `password` is user defined password (taken from user input), `salt` is random string (any length) defined globally in configuration and `hash` is secure hash function. Thus, the following configuration
```csv
"username","password","access"
"username","$6$rounds=656000$mWBiecMPrHAL1VgX$oU4Y5HH8HzlvMaxwkNEJjK13ozElyU1wAHBoO/WW5dAaE4YEfnB0X3FxbynKMl4FBdC3Ovap0jINz4LPkNADg0","read"
```
means that there is user `username` with `read` access and password `password` hashed by `sha512` with salt `salt`.
OAuth provider uses library definitions (`aioauth-client`) in order _authenticate_ users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override `check_credentials` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature is used by service reporting (aka robots).
In order to configure users there are special commands.
## Remote synchronization
There are several supported synchronization providers, currently they are `rsync`, `s3`, `github`.
`rsync` provider does not have any specific logic except for running external rsync application with configured arguments. The service does not handle SSH configuration, thus it has to be configured before running application manually.
`s3` provider uses `boto3` package and implements sync feature. The files are stored in architecture directory (e.g. if bucket is `repository`, packages will be stored in `repository/x86_64` for the `x86_64` architecture), bucket must be created before any action and API key must have permissions to write to the bucket. No external configuration required. In order to upload only changed files the service compares calculated hashes with the Amazon ETags, used realization is described [here](https://teppen.io/2018/10/23/aws_s3_verify_etags/).
`github` provider authenticates through basic auth, API key with repository write permissions is required. There will be created a release with the name of the architecture in case if it does not exist; files will be uploaded to the release assets. It also stores array of files and their MD5 checksums in release body in order to upload only changed ones. According to the Github API in case if there is already uploaded asset with the same name (e.g. database files), asset will be removed first.
## Additional features
Some features require optional dependencies to be installed:
* Version control executables (e.g. `git`, `svn`) for VCS packages.
* `gnupg` application for package and repository sign feature.
* `rsync` application for rsync based repository sync.
* `boto3` python package for `S3` sync.
* `Jinja2` python package for HTML report generation (it is also used by web application).
# Web application
Web application requires the following python packages to be installed:
* Core part requires `aiohttp` (application itself), `aiohttp_jinja2` and `Jinja2` (HTML generation from templates).
* In addition, `aiohttp_debugtoolbar` is required for debug panel. Please note that this option does not work together with authorization and basically must not be used in production.
* In addition, authorization feature requires `aiohttp_security`, `aiohttp_session` and `cryptography`.
* In addition to base authorization dependencies, OAuth2 also requires `aioauth-client` library.
## Middlewares
Service provides some custom middlewares, e.g. logging every exception (except for user ones) and user authorization.
## Web views
All web views are defined in separated package and derived from `ahriman.web.views.base.Base` class which provides typed interfaces for web application.
REST API supports both form and JSON data, but the last one is recommended.
Different APIs are separated into different packages:
* `ahriman.web.views.service` provides views for application controls.
* `ahriman.web.views.status` package provides REST API for application reporting.
* `ahriman.web.views.user` package provides login and logout methods which can be called without authorization.
## Templating
Package provides base jinja templates which can be overridden by settings. Vanilla templates are actively using bootstrap library.
## Requests and scopes
Service provides optional authorization which can be turned on in settings. In order to control user access there are two levels of authorization - read-only (only GET-like requests) and write (anything) which are provided by each web view directly.
If this feature is configured any request will be prohibited without authentication. In addition, configuration flag `auth.safe_build_status` can be used in order to allow seeing main page without authorization.
For authenticated users it uses encrypted session cookies to store tokens; encryption key is generated each time at the start of the application. It also stores expiration time of the session inside.
## External calls
Web application provides external calls to control main service. It spawns child process with specific arguments and waits for its termination. This feature must be used either with authorization or in safe (i.e. when status page is not available world-wide) environment.

272
docs/architecture.rst Normal file
View File

@ -0,0 +1,272 @@
Architecture
============
Package structure
-----------------
Packages have strict rules of importing:
* ``ahriman.application`` package must not be used anywhere except for itself.
* ``ahriman.core`` and ``ahriman.models`` packages don't have any import restriction. Actually we would like to totally restrict importing of ``core`` package from ``models``, but it is impossible at the moment.
* ``ahriman.web`` package is allowed to be imported from ``ahriman.application`` (web handler only, only ``ahriman.web.web`` methods). It also must not be imported globally, only local import is allowed.
Full dependency diagram:
.. image:: ahriman-architecture.svg
:target: _images/ahriman-architecture.svg
:alt: architecture
``ahriman.application`` package
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This package contains application (aka executable) related classes and everything for that. It also contains package called ``ahriman.application.handlers`` in which all available subcommands are described as separated classes derived from base ``ahriman.application.handlers.handler.Handler`` class.
``ahriman.application.application.application.Application`` (god class) is used for any interaction from parsers with repository, web etc. It is divided into multiple traits by functions (package related and repository related) in the same package.
``ahriman.application.ahriman`` contains only command line parses and executes specified ``Handler`` on success, ``ahriman.application.lock.Lock`` is additional class which provides file-based lock and also performs some common checks.
``ahriman.core`` package
^^^^^^^^^^^^^^^^^^^^^^^^
This package contains everything which is required for any time of application run and separated to several packages:
* ``ahriman.core.alpm`` package controls pacman related functions. It provides wrappers for ``pyalpm`` library and safe calls for repository tools (``repo-add`` and ``repo-remove``). Also this package contains ``ahriman.core.alpm.remote`` package which provides wrapper for remote sources (e.g. AUR RPC and official repositories RPC).
* ``ahriman.core.auth`` package provides classes for authorization methods used by web mostly. Base class is ``ahriman.core.auth.auth.Auth`` which must be called by ``load`` method.
* ``ahriman.core.build_tools`` is a package which provides wrapper for ``devtools`` commands.
* ``ahriman.core.database`` is everything including data and schema migrations for database.
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
* ``ahriman.core.report`` is a package with reporting classes. Usually it must be called by ``ahriman.core.report.report.Report.load`` method.
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.repository.Repository`` class) implementation.
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
* ``ahriman.core.status`` contains helpers and watcher class which are required for web application. Reporter must be initialized by using ``ahriman.core.status.client.Client.load`` method.
* ``ahriman.core.upload`` package provides sync feature, must be called by ``ahriman.core.upload.upload.Upload.load`` method.
This package also provides some generic functions and classes which may be used by other packages:
* ``ahriman.core.configuration.Configuration`` is an extension for standard ``configparser`` library.
* ``ahriman.core.exceptions`` provides custom exceptions.
* ``ahriman.core.spawn.Spawn`` is a tool which can spawn another ``ahriman`` process. This feature is used by web application.
* ``ahriman.core.tree`` is a dependency tree implementation.
``ahriman.models`` package
^^^^^^^^^^^^^^^^^^^^^^^^^^
It provides models for any other part of application. Unlike ``ahriman.core`` package classes from here provides only conversion methods (e.g. create class from another or convert to). Mostly case classes and enumerations.
``ahriman.web`` package
^^^^^^^^^^^^^^^^^^^^^^^
Web application. It is important that this package is isolated from any other to allow it to be optional feature (i.e. dependencies which are required by the package are optional).
* ``ahriman.web.middlewares`` provides middlewares for request handlers.
* ``ahriman.web.views`` contains web views derived from aiohttp view class.
* ``ahriman.web.routes`` creates routes for web application.
* ``ahriman.web.web`` provides main web application functions (e.g. start, initialization).
Application run
---------------
* Parse command line arguments, find command and related handler which is set by parser.
* Call ``Handler.execute`` method.
* Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or process in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Web``) which should be able to spawn child process in daemon mode (it is impossible to do for daemonic processes).
* In each child process call lock functions.
* After success checks pass control to ``Handler.run`` method defined by specific handler class.
* Return result (success or failure) of each subprocess and exit from application.
* Some handlers may override their status and throw ``ExitCode`` exception. This exception is just silently suppressed and changes application exit code to ``1``.
In most cases handlers spawn god class ``ahriman.application.application.Application`` class and call required methods.
Application is designed to run from ``systemd`` services and provides parametrized by architecture timer and service file for that.
Database
--------
The service uses SQLite database in order to store some internal info.
Database instance
^^^^^^^^^^^^^^^^^
All methods related to specific part of database (basically operations per table) are split into different traits located inside ``ahriman.core.database.operations`` package. The base trait ``ahriman.core.database.operations.operations.Operations`` also provides generic methods for database access (e.g. row converters and transactional support).
The ``ahriman.core.database.sqlite.SQLite`` class itself derives from all of these traits and implements methods for initialization, including migrations.
Schema and data migrations
^^^^^^^^^^^^^^^^^^^^^^^^^^
The schema migration are applied according to current ``pragma user_info`` values, located at ``ahriman.core.database.migrations`` package and named as ``m000_migration_name.py`` (the preceding ``m`` is required in order to import migration content for tests). Additional class ``ahriman.core.database.migrations.Migrations`` reads all migrations autmatically and applies them in alphabetical order.
There are also data migrations which are located at ``ahriman.core.database.data`` package and move data from old-style (e.g. json files in filesystem, directory trees, etc) to the database. They are also part of migration and (unlike schema migrations) are applied only at specific version breakpoints (e.g. if ``user_version`` is more than 0 no initial migration will be applied).
Type conversions
^^^^^^^^^^^^^^^^
By default, it parses rows into python dictionary. In addition, the following pseudo-types are supported:
* ``Dict[str, Any]``, ``List[Any]`` - for storing JSON data structures in database (technically there is no restriction on types for dictionary keys and values, but it is recommended to use only string keys). The type is stored as ``json`` datatype and ``json.loads`` and ``json.dumps`` methods are used in order to read and write from/to database respectively.
Basic flows
-----------
Add new packages or rebuild existing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Idea is to copy package to the directory from which it will be handled at the next update run. Different variants are supported:
* If supplied argument is file then application moves the file to the directory with built packages. Same rule applies for directory, but in this case it copies every package-like file from the specified directory.
* If supplied argument is directory and there is ``PKGBUILD`` file there it will be treated as local package. In this case it will queue this package to build and copy source files (``PKGBUILD`` and ``.SRCINFO``) to caches.
* If supplied argument iis not file then application tries to lookup for the specified name in AUR and clones it into the directory with manual updates. This scenario can also handle package dependencies which are missing in repositories.
This logic can be overwritten by specifying the ``source`` parameter, which is partially useful if you would like to add package from AUR, but there is local directory cloned from AUR.
Rebuild packages
^^^^^^^^^^^^^^^^
Same as add function for every package in repository. Optional filter by reverse dependency can be supplied.
Remove packages
^^^^^^^^^^^^^^^
This flow removes package from filesystem, updates repository database and also runs synchronization and reporting methods.
Update packages
^^^^^^^^^^^^^^^
This feature is divided into to stages: check AUR for updates and run rebuild for required packages. Whereas check does not do anything except for check itself, update flow is the following:
#. Process every built package first. Those packages are usually added manually.
#. Run sync and report methods.
#. Generate dependency tree for packages to be built.
#. For each level of tree it does:
#. Download package data from AUR.
#. Build every package in clean chroot.
#. Sign packages if required.
#. Add packages to database and sign database if required.
#. Process triggers.
After any step any package data is being removed.
Core functions reference
------------------------
Configuration
^^^^^^^^^^^^^
``ahriman.core.configuration.Configuration`` class provides some additional methods (e.g. ``getpath`` and ``getlist``) and also combines multiple files into single configuration dictionary using architecture overrides. It is the recommended way to deal with settings.
Utils
^^^^^
For every external command run (which is actually not recommended if possible) custom wrapper for ``subprocess`` is used. Additional functions ``ahriman.core.auth.helpers`` provide safe calls for ``aiohttp_security`` methods and are required to make this dependency optional.
Submodules
^^^^^^^^^^
Some packages provide different behaviour depending on configuration settings. In these cases inheritance is used and recommended way to deal with them is to call class method ``load`` from base classes.
Authorization
^^^^^^^^^^^^^
The package provides several authorization methods: disabled, based on configuration and OAuth2.
Disabled (default) authorization provider just allows everything for everyone and does not have any specific configuration (it uses some default configuration parameters though). It also provides generic interface for derived classes.
Mapping (aka configuration) provider uses hashed passwords with salt from the database in order to authenticate users. This provider also enables user permission checking (read/write) (authorization). Thus, it defines the following methods:
* ``check_credentials`` - user password validation (authentication).
* ``verify_access`` - user permission validation (authorization).
Passwords must be stored in database as ``hash(password + salt)``, where ``password`` is user defined password (taken from user input), ``salt`` is random string (any length) defined globally in configuration and ``hash`` is secure hash function. Thus, the following configuration
.. code-block::
"username","password","access"
"username","$6$rounds=656000$mWBiecMPrHAL1VgX$oU4Y5HH8HzlvMaxwkNEJjK13ozElyU1wAHBoO/WW5dAaE4YEfnB0X3FxbynKMl4FBdC3Ovap0jINz4LPkNADg0","read"
means that there is user ``username`` with ``read`` access and password ``password`` hashed by ``sha512`` with salt ``salt``.
OAuth provider uses library definitions (``aioauth-client``) in order *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature is used by service reporting (aka robots).
In order to configure users there are special commands.
Triggers
^^^^^^^^
Triggers are extensions which can be used in order to perform any actions after the update process. The package provides two default extensions - one is report generation and another one is remote upload feature.
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it.
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``List[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
For more details how to deal with the triggers, refer to :doc:`documentation <triggers>` and modules descriptions.
Remote synchronization
^^^^^^^^^^^^^^^^^^^^^^
There are several supported synchronization providers, currently they are ``rsync``, ``s3``, ``github``.
``rsync`` provider does not have any specific logic except for running external rsync application with configured arguments. The service does not handle SSH configuration, thus it has to be configured before running application manually.
``s3`` provider uses ``boto3`` package and implements sync feature. The files are stored in architecture directory (e.g. if bucket is ``repository``, packages will be stored in ``repository/x86_64`` for the ``x86_64`` architecture), bucket must be created before any action and API key must have permissions to write to the bucket. No external configuration required. In order to upload only changed files the service compares calculated hashes with the Amazon ETags, used realization is described `here <https://teppen.io/2018/10/23/aws_s3_verify_etags/>`_.
``github`` provider authenticates through basic auth, API key with repository write permissions is required. There will be created a release with the name of the architecture in case if it does not exist; files will be uploaded to the release assets. It also stores array of files and their MD5 checksums in release body in order to upload only changed ones. According to the Github API in case if there is already uploaded asset with the same name (e.g. database files), asset will be removed first.
Additional features
^^^^^^^^^^^^^^^^^^^
Some features require optional dependencies to be installed:
* Version control executables (e.g. ``git``, ``svn``) for VCS packages.
* ``gnupg`` application for package and repository sign feature.
* ``rsync`` application for rsync based repository sync.
* ``boto3`` python package for ``S3`` sync.
* ``Jinja2`` python package for HTML report generation (it is also used by web application).
Web application
---------------
Web application requires the following python packages to be installed:
* Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates).
* In addition, ``aiohttp_debugtoolbar`` is required for debug panel. Please note that this option does not work together with authorization and basically must not be used in production.
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
Middlewares
^^^^^^^^^^^
Service provides some custom middlewares, e.g. logging every exception (except for user ones) and user authorization.
Web views
^^^^^^^^^
All web views are defined in separated package and derived from ``ahriman.web.views.base.Base`` class which provides typed interfaces for web application.
REST API supports both form and JSON data, but the last one is recommended.
Different APIs are separated into different packages:
* ``ahriman.web.views.service`` provides views for application controls.
* ``ahriman.web.views.status`` package provides REST API for application reporting.
* ``ahriman.web.views.user`` package provides login and logout methods which can be called without authorization.
Templating
^^^^^^^^^^
Package provides base jinja templates which can be overridden by settings. Vanilla templates are actively using bootstrap library.
Requests and scopes
^^^^^^^^^^^^^^^^^^^
Service provides optional authorization which can be turned on in settings. In order to control user access there are two levels of authorization - read-only (only GET-like requests) and write (anything) which are provided by each web view directly.
If this feature is configured any request will be prohibited without authentication. In addition, configuration flag ``auth.safe_build_status`` can be used in order to allow seeing main page without authorization.
For authenticated users it uses encrypted session cookies to store tokens; encryption key is generated each time at the start of the application. It also stores expiration time of the session inside.
External calls
^^^^^^^^^^^^^^
Web application provides external calls to control main service. It spawns child process with specific arguments and waits for its termination. This feature must be used either with authorization or in safe (i.e. when status page is not available world-wide) environment.

10
docs/command-line.rst Normal file
View File

@ -0,0 +1,10 @@
Commands reference
==================
ahriman
-------
.. argparse::
:module: ahriman.application.ahriman
:func: _parser
:prog: ahriman

108
docs/conf.py Normal file
View File

@ -0,0 +1,108 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
from pathlib import Path
from unittest import mock
from ahriman.version import __version__
basedir = Path(__file__).resolve().parent.parent / "src"
sys.path.insert(0, str(basedir))
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
for module in (
"aioauth_client",
"aiohttp",
"aiohttp.web",
"aiohttp.web_exceptions",
"aiohttp.web_response",
"aiohttp.web_urldispatcher",
"aiohttp_jinja2",
"aiohttp_security",
"aiohttp_session",
"aiohttp_session.cookie_storage",
"boto3",
"cryptography",
"pyalpm",
):
sys.modules[module] = mock.Mock()
# -- Project information -----------------------------------------------------
project = "ahriman"
copyright = "2021-2022, ahriman team"
author = "ahriman team"
# The full version, including alpha/beta/rc tags
release = __version__
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named "sphinx.ext.*") or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinxarg.ext",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "default" if on_rtd else "alabaster"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
add_module_names = False
modindex_common_prefix = ["ahriman.application.", "ahriman.core.", "ahriman.models.", "ahriman.web."]
# -- Extension configuration -------------------------------------------------
autoclass_content = "both"
autodoc_member_order = "groupwise"
autodoc_default_options = {
"no-undoc-members": True,
"special-members": "__init__",
}

View File

@ -1,188 +0,0 @@
# ahriman configuration
Some groups can be specified for each architecture separately. E.g. if there are `build` and `build:x86_64` groups it will use the option from `build:x86_64` for the `x86_64` architecture and `build` for any other (architecture specific group has higher priority). In case if both groups are presented, architecture specific options will be merged into global ones overriding them.
There are two variable types which have been added to default ones, they are paths and lists. List values will be read in the same way as shell does:
* By default, it splits value by spaces excluding empty elements.
* In case if quotation mark (`"` or `'`) will be found, any spaces inside will be ignored.
* In order to use quotation mark inside value it is required to put it to another quotation mark, e.g. `wor"'"d "with quote"` will be parsed as `["wor'd", "with quote"]` and vice versa.
* Unclosed quotation mark is not allowed and will rise an exception.
Path values, except for casting to `pathlib.Path` type, will be also expanded to absolute paths relative to the configuration path. E.g. if path is set to `ahriman.ini.d/logging.ini` and root configuration path is `/etc/ahriman.ini`, the value will be expanded to `/etc/ahriman.ini.d/logging.ini`. In order to disable path expand, use the full path, e.g. `/etc/ahriman.ini.d/logging.ini`.
## `settings` group
Base configuration settings.
* `include` - path to directory with configuration files overrides, string, required.
* `database` - path to SQLite database, string, required.
* `logging` - path to logging configuration, string, required. Check `logging.ini` for reference.
## `alpm` group
libalpm and AUR related configuration.
* `aur_url` - base url for AUR, string, required.
* `database` - path to pacman local database cache, string, required.
* `repositories` - list of pacman repositories, space separated list of strings, required.
* `root` - root for alpm library, string, required.
## `auth` group
Base authorization settings. `OAuth` provider requires `aioauth-client` library to be installed.
* `target` - specifies authorization provider, string, optional, default `disabled`. Allowed values are `disabled`, `configuration`, `oauth`.
* `client_id` - OAuth2 application client ID, string, required in case if `oauth` is used.
* `client_secret` - OAuth2 application client secret key, string, required in case if `oauth` is used.
* `max_age` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
* `oauth_provider` - OAuth2 provider class name as is in `aioauth-client` (e.g. `GoogleClient`, `GithubClient` etc), string, required in case if `oauth` is used.
* `oauth_scopes` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. `https://www.googleapis.com/auth/userinfo.email` for `GoogleClient` or `user:email` for `GithubClient`, space separated list of strings, required in case if `oauth` is used.
* `safe_build_status` - allow requesting status page without authorization, boolean, required.
* `salt` - password hash salt, string, required in case if authorization enabled (automatically generated by `create-user` subcommand).
Authorized users are stored inside internal database, if any of external provides are used the password field for non-service users must be empty.
## `build:*` groups
Build related configuration. Group name can refer to architecture, e.g. `build:x86_64` can be used for x86_64 architecture specific settings.
* `archbuild_flags` - additional flags passed to `archbuild` command, space separated list of strings, optional.
* `build_command` - default build command, string, required.
* `ignore_packages` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
* `makepkg_flags` - additional flags passed to `makepkg` command, space separated list of strings, optional.
* `makechrootpkg_flags` - additional flags passed to `makechrootpkg` command, space separated list of strings, optional.
## `repository` group
Base repository settings.
* `name` - repository name, string, required.
* `root` - root path for application, string, required.
## `sign:*` groups
Settings for signing packages or repository. Group name can refer to architecture, e.g. `sign:x86_64` can be used for x86_64 architecture specific settings.
* `target` - configuration flag to enable signing, space separated list of strings, required. Allowed values are `package` (sign each package separately), `repository` (sign repository database file).
* `key` - default PGP key, string, required. This key will also be used for database signing if enabled.
* `key_*` settings - PGP key which will be used for specific packages, string, optional. For example, if there is `key_yay` option the specified key will be used for yay package and default key for others.
## `report` group
Report generation settings.
* `target` - list of reports to be generated, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. `somerandomname` must point to existing section, `email` must point to one of `email` of `email:x86_64` (the one with architecture has higher priority).
Type will be read from several ways:
* In case if `type` option set inside the section, it will be used.
* Otherwise, it will look for type from section name removing architecture name.
* And finally, it will use section name as type.
### `console` type
Section name must be either `console` (plus optional architecture name, e.g. `console:x86_64`) or random name with `type` set.
* `use_utf` - use utf8 symbols in output if set and ascii otherwise, boolean, optional, default `yes`.
### `email` type
Section name must be either `email` (plus optional architecture name, e.g. `email:x86_64`) or random name with `type` set.
* `type` - type of the report, string, optional, must be set to `email` if exists.
* `full_template_path` - path to Jinja2 template for full package description index, string, optional.
* `homepage` - link to homepage, string, optional.
* `host` - SMTP host for sending emails, string, required.
* `link_path` - prefix for HTML links, string, required.
* `no_empty_report` - skip report generation for empty packages list, boolean, optional, default `yes`.
* `password` - SMTP password to authenticate, string, optional.
* `port` - SMTP port for sending emails, int, required.
* `receivers` - SMTP receiver addresses, space separated list of strings, required.
* `sender` - SMTP sender address, string, required.
* `ssl` - SSL mode for SMTP connection, one of `ssl`, `starttls`, `disabled`, optional, default `disabled`.
* `template_path` - path to Jinja2 template, string, required.
* `user` - SMTP user to authenticate, string, optional.
### `html` type
Section name must be either `html` (plus optional architecture name, e.g. `html:x86_64`) or random name with `type` set.
* `type` - type of the report, string, optional, must be set to `html` if exists.
* `path` - path to html report file, string, required.
* `homepage` - link to homepage, string, optional.
* `link_path` - prefix for HTML links, string, required.
* `template_path` - path to Jinja2 template, string, required.
### `telegram` type
Section name must be either `telegram` (plus optional architecture name, e.g. `telegram:x86_64`) or random name with `type` set.
* `type` - type of the report, string, optional, must be set to `telegram` if exists.
* `api_key` - telegram bot API key, string, required. Please refer FAQ about how to create chat and bot
* `chat_id` - telegram chat id, either string with `@` or integer value, required.
* `homepage` - link to homepage, string, optional.
* `link_path` - prefix for HTML links, string, required.
* `template_path` - path to Jinja2 template, string, required.
* `template_type` - `parse_mode` to be passed to telegram API, one of `MarkdownV2`, `HTML`, `Markdown`, string, optional, default `HTML`.
## `upload` group
Remote synchronization settings.
* `target` - list of synchronizations to be used, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. `somerandomname` must point to existing section, `github` must point to one of `github` of `github:x86_64` (with architecture it has higher priority).
Type will be read from several ways:
* In case if `type` option set inside the section, it will be used.
* Otherwise, it will look for type from section name removing architecture name.
* And finally, it will use section name as type.
### `github` type
This feature requires Github key creation (see below). Section name must be either `github` (plus optional architecture name, e.g. `github:x86_64`) or random name with `type` set.
* `type` - type of the upload, string, optional, must be set to `github` if exists.
* `owner` - Github repository owner, string, required.
* `password` - created Github API key. In order to create it do the following:
1. Go to [settings page](https://github.com/settings/profile).
2. Switch to [developers settings](https://github.com/settings/apps).
3. Switch to [personal access tokens](https://github.com/settings/tokens).
4. Generate new token. Required scope is `public_repo` (or `repo` for private repository support).
* `repository` - Github repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
* `username` - Github authorization user, string, required. Basically the same as `owner`.
### `rsync` type
Requires `rsync` package to be installed. Do not forget to configure ssh for user `ahriman`. Section name must be either `rsync` (plus optional architecture name, e.g. `rsync:x86_64`) or random name with `type` set.
* `type` - type of the upload, string, optional, must be set to `rsync` if exists.
* `command` - rsync command to run, space separated list of string, required.
* `remote` - remote server to rsync (e.g. `1.2.3.4:path/to/sync`), string, required.
### `s3` type
Requires `boto3` library to be installed. Section name must be either `s3` (plus optional architecture name, e.g. `s3:x86_64`) or random name with `type` set.
* `type` - type of the upload, string, optional, must be set to `github` if exists.
* `access_key` - AWS access key ID, string, required.
* `bucket` - bucket name (e.g. `bucket`), string, required.
* `chunk_size` - chunk size for calculating entity tags, int, optional, default 8 * 1024 * 1024.
* `region` - bucket region (e.g. `eu-central-1`), string, required.
* `secret_key` - AWS secret access key, string, required.
## `web:*` groups
Web server settings. If any of `host`/`port` is not set, web integration will be disabled. Group name can refer to architecture, e.g. `web:x86_64` can be used for x86_64 architecture specific settings. This feature requires `aiohttp` libraries to be installed.
* `address` - optional address in form `proto://host:port` (`port` can be omitted in case of default `proto` ports), will be used instead of `http://{host}:{port}` in case if set, string, optional. This option is required in case if `OAuth` provider is used.
* `debug` - enable debug toolbar, boolean, optional, default `no`.
* `debug_check_host` - check hosts to access debug toolbar, boolean, optional, default `no`.
* `debug_allowed_hosts` - allowed hosts to get access to debug toolbar, space separated list of string, optional.
* `host` - host to bind, string, optional.
* `index_url` - full url of the repository index page, string, optional.
* `password` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
* `port` - port to bind, int, optional.
* `static_path` - path to directory with static files, string, required.
* `templates` - path to templates directory, string, required.
* `username` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.

207
docs/configuration.rst Normal file
View File

@ -0,0 +1,207 @@
Configuration
=============
Some groups can be specified for each architecture separately. E.g. if there are ``build`` and ``build:x86_64`` groups it will use the option from ``build:x86_64`` for the ``x86_64`` architecture and ``build`` for any other (architecture specific group has higher priority). In case if both groups are presented, architecture specific options will be merged into global ones overriding them.
There are two variable types which have been added to default ones, they are paths and lists. List values will be read in the same way as shell does:
* By default, it splits value by spaces excluding empty elements.
* In case if quotation mark (``"`` or ``'``) will be found, any spaces inside will be ignored.
* In order to use quotation mark inside value it is required to put it to another quotation mark, e.g. ``wor"'"d "with quote"`` will be parsed as ``["wor'd", "with quote"]`` and vice versa.
* Unclosed quotation mark is not allowed and will rise an exception.
Path values, except for casting to ``pathlib.Path`` type, will be also expanded to absolute paths relative to the configuration path. E.g. if path is set to ``ahriman.ini.d/logging.ini`` and root configuration path is ``/etc/ahriman.ini``, the value will be expanded to ``/etc/ahriman.ini.d/logging.ini``. In order to disable path expand, use the full path, e.g. ``/etc/ahriman.ini.d/logging.ini``.
``settings`` group
------------------
Base configuration settings.
* ``include`` - path to directory with configuration files overrides, string, required.
* ``database`` - path to SQLite database, string, required.
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
``alpm`` group
--------------
libalpm and AUR related configuration.
* ``database`` - path to pacman local database cache, string, required.
* ``repositories`` - list of pacman repositories, space separated list of strings, required.
* ``root`` - root for alpm library, string, required.
``auth`` group
--------------
Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` library to be installed.
* ``target`` - specifies authorization provider, string, optional, default ``disabled``. Allowed values are ``disabled``, ``configuration``, ``oauth``.
* ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used.
* ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used.
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
* ``safe_build_status`` - allow requesting status page without authorization, boolean, required.
* ``salt`` - password hash salt, string, required in case if authorization enabled (automatically generated by ``create-user`` subcommand).
Authorized users are stored inside internal database, if any of external provides are used the password field for non-service users must be empty.
``build:*`` groups
------------------
Build related configuration. Group name can refer to architecture, e.g. ``build:x86_64`` can be used for x86_64 architecture specific settings.
* ``archbuild_flags`` - additional flags passed to ``archbuild`` command, space separated list of strings, optional.
* ``build_command`` - default build command, string, required.
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
* ``triggers`` - list of trigger classes (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of mention.
``repository`` group
--------------------
Base repository settings.
* ``name`` - repository name, string, required.
* ``root`` - root path for application, string, required.
``sign:*`` groups
-----------------
Settings for signing packages or repository. Group name can refer to architecture, e.g. ``sign:x86_64`` can be used for x86_64 architecture specific settings.
* ``target`` - configuration flag to enable signing, space separated list of strings, required. Allowed values are ``package`` (sign each package separately), ``repository`` (sign repository database file).
* ``key`` - default PGP key, string, required. This key will also be used for database signing if enabled.
* ``key_*`` settings - PGP key which will be used for specific packages, string, optional. For example, if there is ``key_yay`` option the specified key will be used for yay package and default key for others.
``report`` group
----------------
Report generation settings.
* ``target`` - list of reports to be generated, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. ``somerandomname`` must point to existing section, ``email`` must point to one of ``email`` of ``email:x86_64`` (the one with architecture has higher priority).
Type will be read from several ways:
* In case if ``type`` option set inside the section, it will be used.
* Otherwise, it will look for type from section name removing architecture name.
* And finally, it will use section name as type.
``console`` type
^^^^^^^^^^^^^^^^
Section name must be either ``console`` (plus optional architecture name, e.g. ``console:x86_64``) or random name with ``type`` set.
* ``use_utf`` - use utf8 symbols in output if set and ascii otherwise, boolean, optional, default ``yes``.
``email`` type
^^^^^^^^^^^^^^
Section name must be either ``email`` (plus optional architecture name, e.g. ``email:x86_64``) or random name with ``type`` set.
* ``type`` - type of the report, string, optional, must be set to ``email`` if exists.
* ``full_template_path`` - path to Jinja2 template for full package description index, string, optional.
* ``homepage`` - link to homepage, string, optional.
* ``host`` - SMTP host for sending emails, string, required.
* ``link_path`` - prefix for HTML links, string, required.
* ``no_empty_report`` - skip report generation for empty packages list, boolean, optional, default ``yes``.
* ``password`` - SMTP password to authenticate, string, optional.
* ``port`` - SMTP port for sending emails, int, required.
* ``receivers`` - SMTP receiver addresses, space separated list of strings, required.
* ``sender`` - SMTP sender address, string, required.
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
* ``template_path`` - path to Jinja2 template, string, required.
* ``user`` - SMTP user to authenticate, string, optional.
``html`` type
^^^^^^^^^^^^^
Section name must be either ``html`` (plus optional architecture name, e.g. ``html:x86_64``) or random name with ``type`` set.
* ``type`` - type of the report, string, optional, must be set to ``html`` if exists.
* ``path`` - path to html report file, string, required.
* ``homepage`` - link to homepage, string, optional.
* ``link_path`` - prefix for HTML links, string, required.
* ``template_path`` - path to Jinja2 template, string, required.
``telegram`` type
^^^^^^^^^^^^^^^^^
Section name must be either ``telegram`` (plus optional architecture name, e.g. ``telegram:x86_64``) or random name with ``type`` set.
* ``type`` - type of the report, string, optional, must be set to ``telegram`` if exists.
* ``api_key`` - telegram bot API key, string, required. Please refer FAQ about how to create chat and bot
* ``chat_id`` - telegram chat id, either string with ``@`` or integer value, required.
* ``homepage`` - link to homepage, string, optional.
* ``link_path`` - prefix for HTML links, string, required.
* ``template_path`` - path to Jinja2 template, string, required.
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
``upload`` group
----------------
Remote synchronization settings.
* ``target`` - list of synchronizations to be used, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. ``somerandomname`` must point to existing section, ``github`` must point to one of ``github`` of ``github:x86_64`` (with architecture it has higher priority).
Type will be read from several ways:
* In case if ``type`` option set inside the section, it will be used.
* Otherwise, it will look for type from section name removing architecture name.
* And finally, it will use section name as type.
``github`` type
^^^^^^^^^^^^^^^
This feature requires Github key creation (see below). Section name must be either ``github`` (plus optional architecture name, e.g. ``github:x86_64``) or random name with ``type`` set.
* ``type`` - type of the upload, string, optional, must be set to ``github`` if exists.
* ``owner`` - Github repository owner, string, required.
* ``password`` - created Github API key. In order to create it do the following:
#. Go to `settings page <https://github.com/settings/profile>`_.
#. Switch to `developers settings <https://github.com/settings/apps>`_.
#. Switch to `personal access tokens <https://github.com/settings/tokens>`_.
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
* ``repository`` - Github repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
* ``username`` - Github authorization user, string, required. Basically the same as ``owner``.
``rsync`` type
^^^^^^^^^^^^^^
Requires ``rsync`` package to be installed. Do not forget to configure ssh for user ``ahriman``. Section name must be either ``rsync`` (plus optional architecture name, e.g. ``rsync:x86_64``) or random name with ``type`` set.
* ``type`` - type of the upload, string, optional, must be set to ``rsync`` if exists.
* ``command`` - rsync command to run, space separated list of string, required.
* ``remote`` - remote server to rsync (e.g. ``1.2.3.4:path/to/sync``), string, required.
``s3`` type
^^^^^^^^^^^
Requires ``boto3`` library to be installed. Section name must be either ``s3`` (plus optional architecture name, e.g. ``s3:x86_64``) or random name with ``type`` set.
* ``type`` - type of the upload, string, optional, must be set to ``github`` if exists.
* ``access_key`` - AWS access key ID, string, required.
* ``bucket`` - bucket name (e.g. ``bucket``), string, required.
* ``chunk_size`` - chunk size for calculating entity tags, int, optional, default 8 * 1024 * 1024.
* ``region`` - bucket region (e.g. ``eu-central-1``), string, required.
* ``secret_key`` - AWS secret access key, string, required.
``web:*`` groups
----------------
Web server settings. If any of ``host``/``port`` is not set, web integration will be disabled. Group name can refer to architecture, e.g. ``web:x86_64`` can be used for x86_64 architecture specific settings. This feature requires ``aiohttp`` libraries to be installed.
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
* ``debug`` - enable debug toolbar, boolean, optional, default ``no``.
* ``debug_check_host`` - check hosts to access debug toolbar, boolean, optional, default ``no``.
* ``debug_allowed_hosts`` - allowed hosts to get access to debug toolbar, space separated list of string, optional.
* ``host`` - host to bind, string, optional.
* ``index_url`` - full url of the repository index page, string, optional.
* ``password`` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
* ``port`` - port to bind, int, optional.
* ``static_path`` - path to directory with static files, string, required.
* ``templates`` - path to templates directory, string, required.
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.

View File

@ -1,579 +0,0 @@
# FAQ
## General topics
### What is the purpose of the project?
This project has been created in order to maintain self-hosted Arch Linux user repository without manual intervention - checking for updates and building packages.
### How do I install it?
TL;DR
```shell
yay -S ahriman
sudo ahriman -a x86_64 repo-setup --packager "ahriman bot <ahriman@example.com>" --repository "repository"
systemctl enable --now ahriman@x86_64.timer
```
#### Long answer
The idea is to install the package as usual, create working directory tree, create configuration for `sudo` and `devtools`. Detailed description of the setup instruction can be found [here](setup.md).
### What does "architecture specific" mean? / How to configure for different architectures?
Some sections can be configured per architecture. The service will merge architecture specific values into common settings. In order to specify settings for specific architecture you must point it in section name.
For example, the section
```ini
[build]
build_command = extra-x86_64-build
```
states that default build command is `extra-x86_64-build`. But if there is section
```ini
[build:i686]
build_command = extra-i686-build
```
the `extra-i686-build` command will be used for `i686` architecture.
### How to use reporter/upload settings?
Normally you probably like to generate only one report for the specific type, e.g. only one email report. In order to do it you will need to have the following configuration:
```ini
[report]
target = email
[email]
...
```
or in case of multiple architectures and _different_ reporting settings:
```ini
[report]
target = email
[email:i686]
...
[email:x86_64]
...
```
But for some cases you would like to have multiple different reports with the same type (e.g. sending different templates to different addresses). For these cases you will need to specify section name in target and type in section, e.g. the following configuration can be used:
```ini
[report]
target = email_1 email_2
[email_1]
type = email
...
[email_2]
type = email
...
```
### Okay, I've installed ahriman, how do I add new package?
```shell
sudo -u ahriman ahriman package-add ahriman --now
```
`--now` flag is totally optional and just run `repo-update` subcommand after the registering the new package, Thus the extended flow is the following:
```shell
sudo -u ahriman ahriman package-add ahriman
sudo -u ahriman ahriman repo-update
```
### AUR is fine, but I would like to create package from local PKGBUILD
TL;DR
```shell
sudo -u ahriman ahriman package-add /path/to/local/directory/with/PKGBUILD --now
```
Before using this command you will need to create local directory, put `PKGBUILD` there and generate `.SRCINFO` by using `makepkg --printsrcinfo > .SRCINFO` command. These packages will be stored locally and _will be ignored_ during automatic update; in order to update the package you will need to run `package-add` command again.
### But I just wanted to change PKGBUILD from AUR a bit!
Well it is supported also.
1. Clone sources from AUR.
2. Make changes you would like to (e.g. edit `PKGBUILD`, add external patches).
3. Run `sudo -u ahriman ahriman patch-add /path/to/local/directory/with/PKGBUILD`.
The last command will calculate diff from current tree to the `HEAD` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
### Package build fails because it cannot validate PGP signature of source files
TL;DR
```shell
sudo -u ahriman ahriman key-import ...
```
### How do I check if there are new commits for VCS packages?
Normally the service handles VCS packages correctly, but it requires additional dependencies:
```shell
pacman -S breezy darcs mercurial subversion
```
### I would like to remove package because it is no longer needed/moved to official repositories
```shell
sudo -u ahriman ahriman package-remove ahriman
```
Also, there is command `repo-remove-unknown` which checks packages in AUR and local storage and removes ones which have been removed.
Remove commands also remove any package files (patches, caches etc).
### There is new major release of %library-name%, how do I rebuild packages?
TL;DR
```shell
sudo -u ahriman ahriman repo-rebuild --depends-on python
```
You can even rebuild the whole repository (which is particular useful in case if you would like to change packager) if you do not supply `--depends-on` option.
However, note that you do not need to rebuild repository in case if you just changed signing option, just use `repo-sign` command instead.
### Hmm, I have packages built, but how can I use it?
Add the following lines to your `pacman.conf`:
```ini
[repository]
Server = file:///var/lib/ahriman/repository/x86_64
```
(You might need to add `SigLevel` option according to the pacman documentation.)
### I would like to serve the repository
Easy. For example, nginx configuration (without SSL) will look like:
```
server {
listen 80;
server_name repo.example.com;
location / {
autoindex on;
root /var/lib/ahriman/repository;
}
}
```
Example of the status page configuration is the following (status service is using 8080 port):
```
server {
listen 80;
server_name builds.example.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarder-Proto $scheme;
proxy_pass http://127.0.0.1:8080;
}
}
```
## Docker image
We provide official images which can be found under `arcan1s/ahriman` repository. Docker image is being updated on each master commit as well as on each version. If you would like to use last (probably unstable build) you can use `latest` tag; otherwise you can use any version tag available.
The default action (in case if no arguments provided) is `repo-update`. Basically the idea is to run container, e.g.:
```shell
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
```
`--privileged` flag is required to make mount possible inside container. In addition, you can pass own configuration overrides by using the same `-v` flag, e.g.:
```shell
docker run -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
```
By default, it runs `repo-update`, but it can be overwritten to any other command you would like to, e.g.:
```shell
docker run arcan1s/ahriman:latest package-add ahriman --now
```
For more details please refer to docker FAQ.
### Environment variables
The following environment variables are supported:
* `AHRIMAN_ARCHITECTURE` - architecture of the repository, default is `x86_64`.
* `AHRIMAN_DEBUG` - if set all commands will be logged to console.
* `AHRIMAN_FORCE_ROOT` - force run ahriman as root instead of guessing by subcommand.
* `AHRIMAN_HOST` - host for the web interface, default is `0.0.0.0`.
* `AHRIMAN_OUTPUT` - controls logging handler, e.g. `syslog`, `console`. The name must be found in logging configuration. Note that if `syslog` (the default) handler is used you will need to mount `/dev/log` inside container because it is not available there.
* `AHRIMAN_PACKAGER` - packager name from which packages will be built, default is `ahriman bot <ahriman@example.com>`.
* `AHRIMAN_PORT` - HTTP server port if any, default is empty.
* `AHRIMAN_REPOSITORY` - repository name, default is `aur-clone`.
* `AHRIMAN_REPOSITORY_ROOT` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses `ahriman` directory inside ahriman's home, which can be passed as mount volume.
* `AHRIMAN_USER` - ahriman user, usually must not be overwritten, default is `ahriman`.
You can pass any of these variables by using `-e` argument, e.g.:
```shell
docker run -e AHRIMAN_PORT=8080 arcan1s/ahriman:latest
```
### Working with web service
Well for that you would need to have web container instance running forever; it can be achieved by the following command:
```shell
docker run -p 8080:8080 -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
```
Note about `AHRIMAN_PORT` environment variable which is required in order to enable web service. An additional port bind by `-p 8080:8080` is required to pass docker port outside of container.
For every next container run use arguments `-e AHRIMAN_PORT=8080 --net=host`, e.g.:
```shell
docker run --privileged -e AHRIMAN_PORT=8080 --net=host -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
```
## Remote synchronization
### Wait I would like to use the repository from another server
There are several choices:
1. Easy and cheap, just share your local files through the internet, e.g. for `nginx`:
```
server {
location /x86_64 {
root /var/lib/ahriman/repository/x86_64;
autoindex on;
}
}
```
2. You can also upload your packages using `rsync` to any available server. In order to use it you would need to configure ahriman first:
```ini
[upload]
target = rsync
[rsync]
remote = 192.168.0.1:/srv/repo
```
After that just add `/srv/repo` to the `pacman.conf` as usual. You can also upload to S3 (e.g. `Server = https://s3.eu-central-1.amazonaws.com/repository/x86_64`) or to Github (e.g. `Server = https://github.com/ahriman/repository/releases/download/x86_64`).
### How do I configure S3?
1. Install dependencies:
```shell
pacman -S python-boto3
```
3. Create a bucket.
4. Create user with write access to the bucket:
```
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListObjectsInBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::repository"
]
},
{
"Sid": "AllObjectActions",
"Effect": "Allow",
"Action": "s3:*Object",
"Resource": [
"arn:aws:s3:::repository/*"
]
}
]
}
```
5. Create an API key for the user and store it.
6. Configure the service as following:
```ini
[upload]
target = s3
[s3]
access_key = ...
bucket = repository
region = eu-central-1
secret_key = ...
```
### How do I configure Github?
1. Create a repository.
2. [Create API key](https://github.com/settings/tokens) with scope `public_repo`.
3. Configure the service as following:
```ini
[upload]
target = github
[github]
owner = ahriman
password = ...
repository = repository
username = ahriman
```
## Reporting
### I would like to get report to email
1. Install dependencies:
```shell
yay -S python-jinja
```
2. Configure the service:
```ini
[report]
target = email
[email]
host = smtp.example.com
link_path = http://example.com/x86_64
password = ...
port = 465
receivers = me@example.com
sender = me@example.com
user = me@example.com
```
### I'm using synchronization to S3 and would like to generate index page
1. Install dependencies:
```shell
yay -S python-jinja
```
2. Configure the service:
```ini
[report]
target = html
[html]
path = /var/lib/ahriman/repository/x86_64/index.html
link_path = http://example.com/x86_64
```
After these steps `index.html` file will be automatically synced to S3
### I would like to get messages to my telegram account/channel
1. It still requires additional dependencies:
```shell
yay -S python-jinja
```
2. Register bot in telegram. You can do it by using by talking with [@BotFather](https://t.me/botfather). For more details please refer to [official documentation](https://core.telegram.org/bots).
3. Optionally (if you want to post message in chat):
1. Create telegram channel.
2. Invite your bot into the channel.
3. Make your channel public
4. Get chat id if you want to use by numerical id or just use id prefixed with `@` (e.g. `@ahriman`). If you are not using chat the chat id is your user id. If you don't want to make channel public you can use [this guide](https://stackoverflow.com/a/33862907).
5. Configure the service:
```ini
[report]
target = telegram
[telegram]
api_key = aaAAbbBBccCC
chat_id = @ahriman
link_path = http://example.com/x86_64
```
`api_key` is the one sent by [@BotFather](https://t.me/botfather), `chat_id` is the value retrieved from previous step.
If you did everything fine you should receive the message with the next update. Quick credentials check can be done by using the following command:
```shell
curl 'https://api.telegram.org/bot${CHAT_ID}/sendMessage?chat_id=${API_KEY}&text=hello'
```
(replace `${CHAT_ID}` and `${API_KEY}` with the values from configuration).
## Web service
### Readme mentions web interface, how do I use it?
1. Install dependencies:
```shell
yay -S python-aiohttp python-aiohttp-jinja2
```
2. Configure service:
```ini
[web]
port = 8080
```
3. Start the web service `systemctl enable --now ahriman-web@x86_64`.
### I would like to limit user access to the status page
1. Install dependencies 😊:
```shell
yay -S python-aiohttp-security python-aiohttp-session python-cryptography
```
2. Configure the service to enable authorization:
```ini
[auth]
target = configuration
```
3. Create user for the service:
```shell
sudo -u ahriman ahriman user-add --as-service -r write api
```
This command will ask for the password, just type it in stdin; _do not_ leave the field blank, user will not be able to authorize.
4. Create end-user `sudo -u ahriman ahriman user-add -r write my-first-user` with password.
5. Restart web service `systemctl restart ahriman-web@x86_64`.
### I would like to use OAuth
1. Create OAuth web application, download its `client_id` and `client_secret`.
2. Guess what? Install dependencies:
```shell
yay -S python-aiohttp-security python-aiohttp-session python-cryptography python-aioauth-client
```
3. Configure the service:
```ini
[auth]
target = oauth
client_id = ...
client_secret = ...
[web]
address = https://example.com
```
Configure `oauth_provider` and `oauth_scopes` in case if you would like to use different from Google provider. Scope must grant access to user email. `web.address` is required to make callback URL available from internet.
4. Create service user:
```shell
sudo -u ahriman ahriman user-add --as-service -r write api
```
5. Create end-user `sudo -u ahriman ahriman user-add -r write my-first-user`. When it will ask for the password leave it blank.
6. Restart web service `systemctl restart ahriman-web@x86_64`.
## Other topics
### How does it differ from %another-manager%?
Short answer - I do not know.
#### [archrepo2](https://github.com/lilydjwg/archrepo2)
Don't know, haven't tried it. But it lacks of documentation at least.
* Web interface.
* No synchronization and reporting.
* `archrepo2` actively uses direct shell calls and `yaourt` components.
* It has constantly running process instead of timer process (it is not pro or con).
#### [repoctl](https://github.com/cassava/repoctl)
* Web interface.
* No reporting.
* Local packages and patches support.
* Some actions are not fully automated (e.g. package update still requires manual intervention for the build itself).
* `repoctl` has better AUR interaction features. With colors!
* `repoctl` has much easier configuration and even completion.
* `repoctl` is able to store old packages.
* Ability to host repository from same command vs external services (e.g. nginx) in `ahriman`.
#### [repo-scripts](https://github.com/arcan1s/repo-scripts)
Though originally I've created ahriman by trying to improve the project, it still lacks a lot of features:
* Web interface.
* Better reporting with template support.
* Synchronization features (there was only `rsync` based).
* Local packages and patches support.
* No dependency management.
* And so on.
`repo-scripts` also have bad architecture and bad quality code and uses out-of-dated `yaourt` and `package-query`.
### I would like to check service logs
By default, the service writes logs to `/dev/log` which can be accessed by using `journalctl` command (logs are written to the journal of the user under which command is run).
You can also edit configuration and forward logs to `stderr`, just change `handlers` value, e.g.:
```shell
sed -i 's/handlers = syslog_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini
```
You can even configure logging as you wish, but kindly refer to python `logging` module configuration.
### Html customization
It is possible to customize html templates. In order to do so, create files somewhere (refer to Jinja2 documentation and the service source code for available parameters) and put `template_path` to configuration pointing to this directory.
### I did not find my question
[Create an issue](https://github.com/arcan1s/ahriman/issues) with type **Question**.

703
docs/faq.rst Normal file
View File

@ -0,0 +1,703 @@
FAQ
===
General topics
--------------
What is the purpose of the project?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This project has been created in order to maintain self-hosted Arch Linux user repository without manual intervention - checking for updates and building packages.
How do I install it?
^^^^^^^^^^^^^^^^^^^^
TL;DR
.. code-block:: shell
yay -S ahriman
sudo ahriman -a x86_64 repo-setup --packager "ahriman bot <ahriman@example.com>" --repository "repository"
systemctl enable --now ahriman@x86_64.timer
Long answer
"""""""""""
The idea is to install the package as usual, create working directory tree, create configuration for ``sudo`` and ``devtools``. Detailed description of the setup instruction can be found :doc:`here <setup>`.
What does "architecture specific" mean? / How to configure for different architectures?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Some sections can be configured per architecture. The service will merge architecture specific values into common settings. In order to specify settings for specific architecture you must point it in section name.
For example, the section
.. code-block:: ini
[build]
build_command = extra-x86_64-build
states that default build command is ``extra-x86_64-build``. But if there is section
.. code-block:: ini
[build:i686]
build_command = extra-i686-build
the ``extra-i686-build`` command will be used for ``i686`` architecture.
How to use reporter/upload settings?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally you probably like to generate only one report for the specific type, e.g. only one email report. In order to do it you will need to have the following configuration:
.. code-block:: ini
[report]
target = email
[email]
...
or in case of multiple architectures and *different* reporting settings:
.. code-block:: ini
[report]
target = email
[email:i686]
...
[email:x86_64]
...
But for some cases you would like to have multiple different reports with the same type (e.g. sending different templates to different addresses). For these cases you will need to specify section name in target and type in section, e.g. the following configuration can be used:
.. code-block:: ini
[report]
target = email_1 email_2
[email_1]
type = email
...
[email_2]
type = email
...
Okay, I've installed ahriman, how do I add new package?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: shell
sudo -u ahriman ahriman package-add ahriman --now
``--now`` flag is totally optional and just run ``repo-update`` subcommand after the registering the new package, Thus the extended flow is the following:
.. code-block:: shell
sudo -u ahriman ahriman package-add ahriman
sudo -u ahriman ahriman repo-update
AUR is fine, but I would like to create package from local PKGBUILD
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TL;DR
.. code-block:: shell
sudo -u ahriman ahriman package-add /path/to/local/directory/with/PKGBUILD --now
Before using this command you will need to create local directory, put ``PKGBUILD`` there and generate ``.SRCINFO`` by using ``makepkg --printsrcinfo > .SRCINFO`` command. These packages will be stored locally and *will be ignored* during automatic update; in order to update the package you will need to run ``package-add`` command again.
But I just wanted to change PKGBUILD from AUR a bit!
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Well it is supported also.
#. Clone sources from AUR.
#. Make changes you would like to (e.g. edit ``PKGBUILD``, add external patches).
#. Run ``sudo -u ahriman ahriman patch-add /path/to/local/directory/with/PKGBUILD``.
The last command will calculate diff from current tree to the ``HEAD`` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
Hey, I would like to rebuild the official repository package
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
So it is the same as adding any other package, but due to restrictions you must specify source explicitly, e.g.:
.. code-block:: shell
sudo -u ahriman ahriman package-add pacmann -s repository
Package build fails because it cannot validate PGP signature of source files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TL;DR
.. code-block:: shell
sudo -u ahriman ahriman key-import ...
How do I check if there are new commits for VCS packages?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally the service handles VCS packages correctly, but it requires additional dependencies:
.. code-block:: shell
pacman -S breezy darcs mercurial subversion
I would like to remove package because it is no longer needed/moved to official repositories
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: shell
sudo -u ahriman ahriman package-remove ahriman
Also, there is command ``repo-remove-unknown`` which checks packages in AUR and local storage and removes ones which have been removed.
Remove commands also remove any package files (patches, caches etc).
There is new major release of %library-name%, how do I rebuild packages?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TL;DR
.. code-block:: shell
sudo -u ahriman ahriman repo-rebuild --depends-on python
You can even rebuild the whole repository (which is particular useful in case if you would like to change packager) if you do not supply ``--depends-on`` option.
However, note that you do not need to rebuild repository in case if you just changed signing option, just use ``repo-sign`` command instead.
Hmm, I have packages built, but how can I use it?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add the following lines to your ``pacman.conf``:
.. code-block:: ini
[repository]
Server = file:///var/lib/ahriman/repository/x86_64
(You might need to add ``SigLevel`` option according to the pacman documentation.)
I would like to serve the repository
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Easy. For example, nginx configuration (without SSL) will look like:
.. code-block::
server {
listen 80;
server_name repo.example.com;
location / {
autoindex on;
root /var/lib/ahriman/repository;
}
}
Example of the status page configuration is the following (status service is using 8080 port):
.. code-block::
server {
listen 80;
server_name builds.example.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarder-Proto $scheme;
proxy_pass http://127.0.0.1:8080;
}
}
Docker image
------------
We provide official images which can be found under ``arcan1s/ahriman`` repository. Docker image is being updated on each master commit as well as on each version. If you would like to use last (probably unstable) build you can use ``edge`` tag or ``latest`` for any tagged versions; otherwise you can use any version tag available.
The default action (in case if no arguments provided) is ``repo-update``. Basically the idea is to run container, e.g.:
.. code-block:: shell
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
``--privileged`` flag is required to make mount possible inside container. In addition, you can pass own configuration overrides by using the same ``-v`` flag, e.g.:
.. code-block:: shell
docker run -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
By default, it runs ``repo-update``, but it can be overwritten to any other command you would like to, e.g.:
.. code-block:: shell
docker run arcan1s/ahriman:latest package-add ahriman --now
For more details please refer to docker FAQ.
Environment variables
^^^^^^^^^^^^^^^^^^^^^
The following environment variables are supported:
* ``AHRIMAN_ARCHITECTURE`` - architecture of the repository, default is ``x86_64``.
* ``AHRIMAN_DEBUG`` - if set all commands will be logged to console.
* ``AHRIMAN_FORCE_ROOT`` - force run ahriman as root instead of guessing by subcommand.
* ``AHRIMAN_HOST`` - host for the web interface, default is ``0.0.0.0``.
* ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` (the default) handler is used you will need to mount ``/dev/log`` inside container because it is not available there.
* ``AHRIMAN_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot <ahriman@example.com>``.
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
* ``AHRIMAN_REPOSITORY`` - repository name, default is ``aur-clone``.
* ``AHRIMAN_REPOSITORY_ROOT`` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses ``ahriman`` directory inside ahriman's home, which can be passed as mount volume.
* ``AHRIMAN_USER`` - ahriman user, usually must not be overwritten, default is ``ahriman``.
You can pass any of these variables by using ``-e`` argument, e.g.:
.. code-block:: shell
docker run -e AHRIMAN_PORT=8080 arcan1s/ahriman:latest
Web service setup
^^^^^^^^^^^^^^^^^
Well for that you would need to have web container instance running forever; it can be achieved by the following command:
.. code-block:: shell
docker run -p 8080:8080 -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
Note about ``AHRIMAN_PORT`` environment variable which is required in order to enable web service. An additional port bind by ``-p 8080:8080`` is required to pass docker port outside of container.
For every next container run use arguments ``-e AHRIMAN_PORT=8080 --net=host``, e.g.:
.. code-block:: shell
docker run --privileged -e AHRIMAN_PORT=8080 --net=host -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
Remote synchronization
----------------------
Wait I would like to use the repository from another server
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are several choices:
#.
Easy and cheap, just share your local files through the internet, e.g. for ``nginx``:
.. code-block::
server {
location /x86_64 {
root /var/lib/ahriman/repository/x86_64;
autoindex on;
}
}
#.
You can also upload your packages using ``rsync`` to any available server. In order to use it you would need to configure ahriman first:
.. code-block:: ini
[upload]
target = rsync
[rsync]
remote = 192.168.0.1:/srv/repo
After that just add ``/srv/repo`` to the ``pacman.conf`` as usual. You can also upload to S3 (e.g. ``Server = https://s3.eu-central-1.amazonaws.com/repository/x86_64``) or to Github (e.g. ``Server = https://github.com/ahriman/repository/releases/download/x86_64``).
How do I configure S3?
^^^^^^^^^^^^^^^^^^^^^^
#.
Install dependencies:
.. code-block:: shell
pacman -S python-boto3
#.
Create a bucket.
#.
Create user with write access to the bucket:
.. code-block::
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListObjectsInBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::repository"
]
},
{
"Sid": "AllObjectActions",
"Effect": "Allow",
"Action": "s3:*Object",
"Resource": [
"arn:aws:s3:::repository/*"
]
}
]
}
#.
Create an API key for the user and store it.
#.
Configure the service as following:
.. code-block:: ini
[upload]
target = s3
[s3]
access_key = ...
bucket = repository
region = eu-central-1
secret_key = ...
How do I configure Github?
^^^^^^^^^^^^^^^^^^^^^^^^^^
#.
Create a repository.
#.
`Create API key <https://github.com/settings/tokens>`_ with scope ``public_repo``.
#.
Configure the service as following:
.. code-block:: ini
[upload]
target = github
[github]
owner = ahriman
password = ...
repository = repository
username = ahriman
Reporting
---------
I would like to get report to email
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#.
Install dependencies:
.. code-block:: shell
yay -S python-jinja
#.
Configure the service:
.. code-block:: ini
[report]
target = email
[email]
host = smtp.example.com
link_path = http://example.com/x86_64
password = ...
port = 465
receivers = me@example.com
sender = me@example.com
user = me@example.com
I'm using synchronization to S3 and would like to generate index page
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#.
Install dependencies:
.. code-block:: shell
yay -S python-jinja
#.
Configure the service:
.. code-block:: ini
[report]
target = html
[html]
path = /var/lib/ahriman/repository/x86_64/index.html
link_path = http://example.com/x86_64
After these steps ``index.html`` file will be automatically synced to S3
I would like to get messages to my telegram account/channel
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#.
It still requires additional dependencies:
.. code-block:: shell
yay -S python-jinja
#.
Register bot in telegram. You can do it by talking with `@BotFather <https://t.me/botfather>`_. For more details please refer to `official documentation <https://core.telegram.org/bots>`_.
#.
Optionally (if you want to post message in chat):
#. Create telegram channel.
#. Invite your bot into the channel.
#. Make your channel public
#.
Get chat id if you want to use by numerical id or just use id prefixed with ``@`` (e.g. ``@ahriman``). If you are not using chat the chat id is your user id. If you don't want to make channel public you can use `this guide <https://stackoverflow.com/a/33862907>`_.
#.
Configure the service:
.. code-block:: ini
[report]
target = telegram
[telegram]
api_key = aaAAbbBBccCC
chat_id = @ahriman
link_path = http://example.com/x86_64
``api_key`` is the one sent by `@BotFather <https://t.me/botfather>`_, ``chat_id`` is the value retrieved from previous step.
If you did everything fine you should receive the message with the next update. Quick credentials check can be done by using the following command:
.. code-block:: shell
curl 'https://api.telegram.org/bot${CHAT_ID}/sendMessage?chat_id=${API_KEY}&text=hello'
(replace ``${CHAT_ID}`` and ``${API_KEY}`` with the values from configuration).
Web service
-----------
Readme mentions web interface, how do I use it?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#.
Install dependencies:
.. code-block:: shell
yay -S python-aiohttp python-aiohttp-jinja2
#.
Configure service:
.. code-block:: ini
[web]
port = 8080
#.
Start the web service ``systemctl enable --now ahriman-web@x86_64``.
I would like to limit user access to the status page
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#.
Install dependencies 😊:
.. code-block:: shell
yay -S python-aiohttp-security python-aiohttp-session python-cryptography
#.
Configure the service to enable authorization:
.. code-block:: ini
[auth]
target = configuration
#.
Create user for the service:
.. code-block:: shell
sudo -u ahriman ahriman user-add --as-service -r write api
This command will ask for the password, just type it in stdin; *do not* leave the field blank, user will not be able to authorize.
#.
Create end-user ``sudo -u ahriman ahriman user-add -r write my-first-user`` with password.
#. Restart web service ``systemctl restart ahriman-web@x86_64``.
I would like to use OAuth
^^^^^^^^^^^^^^^^^^^^^^^^^
#.
Create OAuth web application, download its ``client_id`` and ``client_secret``.
#.
Guess what? Install dependencies:
.. code-block:: shell
yay -S python-aiohttp-security python-aiohttp-session python-cryptography python-aioauth-client
#.
Configure the service:
.. code-block:: ini
[auth]
target = oauth
client_id = ...
client_secret = ...
[web]
address = https://example.com
Configure ``oauth_provider`` and ``oauth_scopes`` in case if you would like to use different from Google provider. Scope must grant access to user email. ``web.address`` is required to make callback URL available from internet.
#.
Create service user:
.. code-block:: shell
sudo -u ahriman ahriman user-add --as-service -r write api
#.
Create end-user ``sudo -u ahriman ahriman user-add -r write my-first-user``. When it will ask for the password leave it blank.
#. Restart web service ``systemctl restart ahriman-web@x86_64``.
Backup and restore
------------------
The service provides several commands aim to do easy repository backup and restore. If you would like to move repository from the server ``server1.example.com`` to another ``server2.example.com`` you have to perform the following steps:
#.
On the source server ``server1.example.com`` run ``repo-backup`` command, e.g.:
.. code-block:: shell
sudo ahriman repo-backup /tmp/repo.tar.gz
This command will pack all configuration files together with database file into the archive specified as command line argument (i.e. ``/tmp/repo.tar.gz``). In addition it will also archive ``cache`` directory (the one which contains local clones used by e.g. local packages) and ``.gnupg`` of the ``ahriman`` user.
#.
Copy created archive from source server ``server1.example.com`` to target ``server2.example.com``.
#.
Install ahriman as usual on the target server ``server2.example.com`` if you didn't yet.
#.
Extract archive e.g. by using subcommand:
.. code-block:: shell
sudo ahriman repo-restore /tmp/repo.tar.gz
An additional argument ``-o``/``--output`` can be used to specify extraction root (``/`` by default).
#.
Rebuild repository:
.. code-block:: shell
sudo -u ahriman ahriman repo-rebuild --from-database
Other topics
------------
How does it differ from %another-manager%?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Short answer - I do not know.
`archrepo2 <https://github.com/lilydjwg/archrepo2>`_
""""""""""""""""""""""""""""""""""""""""""""""""""""
Don't know, haven't tried it. But it lacks of documentation at least.
* Web interface.
* No synchronization and reporting.
* ``archrepo2`` actively uses direct shell calls and ``yaourt`` components.
* It has constantly running process instead of timer process (it is not pro or con).
`repoctl <https://github.com/cassava/repoctl>`_
"""""""""""""""""""""""""""""""""""""""""""""""
* Web interface.
* No reporting.
* Local packages and patches support.
* Some actions are not fully automated (e.g. package update still requires manual intervention for the build itself).
* ``repoctl`` has better AUR interaction features. With colors!
* ``repoctl`` has much easier configuration and even completion.
* ``repoctl`` is able to store old packages.
* Ability to host repository from same command vs external services (e.g. nginx) in ``ahriman``.
`repo-scripts <https://github.com/arcan1s/repo-scripts>`_
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Though originally I've created ahriman by trying to improve the project, it still lacks a lot of features:
* Web interface.
* Better reporting with template support.
* Synchronization features (there was only ``rsync`` based).
* Local packages and patches support.
* No dependency management.
* And so on.
``repo-scripts`` also have bad architecture and bad quality code and uses out-of-dated ``yaourt`` and ``package-query``.
`toolbox <https://github.com/chaotic-aur/toolbox>`_
"""""""""""""""""""""""""""""""""""""""""""""""""""
It is automation tools for ``repoctl`` mentioned above. Except for using shell it looks pretty cool and also offers some additional features like patches, remote synchronization (isn't it?) and reporting.
I would like to check service logs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, the service writes logs to ``/dev/log`` which can be accessed by using ``journalctl`` command (logs are written to the journal of the user under which command is run).
You can also edit configuration and forward logs to ``stderr``, just change ``handlers`` value, e.g.:
.. code-block:: shell
sed -i 's/handlers = syslog_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_.
Html customization
^^^^^^^^^^^^^^^^^^
It is possible to customize html templates. In order to do so, create files somewhere (refer to Jinja2 documentation and the service source code for available parameters) and put ``template_path`` to configuration pointing to this directory.
I did not find my question
^^^^^^^^^^^^^^^^^^^^^^^^^^
`Create an issue <https://github.com/arcan1s/ahriman/issues>`_ with type **Question**.

38
docs/index.rst Normal file
View File

@ -0,0 +1,38 @@
Welcome to ahriman's documentation!
===================================
Wrapper for managing custom repository inspired by `repo-scripts <https://github.com/arcan1s/repo-scripts>`_.
Features
--------
* Install-configure-forget manager for own repository.
* Multi-architecture support.
* VCS packages support.
* Sign support with gpg (repository, package, per package settings).
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram) and even ability to write own extensions.
* Dependency manager.
* Ability to patch AUR packages and even create package from local PKGBUILDs.
* Repository status interface with optional authorization and control options.
Contents
--------
.. toctree::
:maxdepth: 2
setup
configuration
command-line
faq
architecture
advanced-usage
triggers
modules
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

7
docs/modules.rst Normal file
View File

@ -0,0 +1,7 @@
Modules
=======
.. toctree::
:maxdepth: 4
ahriman

View File

@ -1,66 +0,0 @@
# Setup instructions
1. Install package as usual.
2. Change settings if required, see [configuration reference](configuration.md) for more details.
3. TL;DR
```shell
sudo ahriman -a x86_64 repo-setup ...
```
`repo-setup` literally does the following steps:
1. Create `/var/lib/ahriman/.makepkg.conf` with `makepkg.conf` overrides if required (at least you might want to set `PACKAGER`):
```shell
echo 'PACKAGER="John Doe <john@doe.com>"' | sudo -u ahriman tee -a /var/lib/ahriman/.makepkg.conf
```
2. Configure build tools (it is required for correct dependency management system):
1. Create build command, e.g. `ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build` (you can choose any name for command, basically it should be `{name}-{arch}-build`).
2. Create configuration file, e.g. `cp /usr/share/devtools/pacman-{extra,ahriman}.conf` (same as previous `pacman-{name}.conf`).
3. Change configuration file, add your own repository, add multilib repository etc;
4. Set `build_command` option to point to your command.
5. Configure `/etc/sudoers.d/ahriman` to allow running command without a password.
```shell
ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build
cp /usr/share/devtools/pacman-{extra,ahriman}.conf
echo '[multilib]' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo 'Include = /etc/pacman.d/mirrorlist' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo '[aur-clone]' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo 'SigLevel = Optional TrustAll' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo '[build]' | tee -a /etc/ahriman.ini.d/build.ini
echo 'build_command = ahriman-x86_64-build' | tee -a /etc/ahriman.ini.d/build.ini
echo 'Cmnd_Alias CARCHBUILD_CMD = /usr/local/bin/ahriman-x86_64-build *' | tee -a /etc/sudoers.d/ahriman
echo 'ahriman ALL=(ALL) NOPASSWD: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
chmod 400 /etc/sudoers.d/ahriman
```
4. Start and enable `ahriman@.timer` via `systemctl`:
```shell
systemctl enable --now ahriman@x86_64.timer
```
5. Start and enable status page:
```shell
systemctl enable --now ahriman-web@x86_64
```
6. Add packages by using `ahriman package-add {package}` command:
```shell
sudo -u ahriman ahriman -a x86_64 package-add ahriman --now
```
## User creation
`user-add` subcommand is recommended for new user creation.

76
docs/setup.rst Normal file
View File

@ -0,0 +1,76 @@
Initial setup
=============
#.
Install package as usual.
#.
Change settings if required, see :doc:`configuration reference <configuration>` for more details.
#.
TL;DR
.. code-block:: shell
sudo ahriman -a x86_64 repo-setup ...
``repo-setup`` literally does the following steps:
#.
Create ``/var/lib/ahriman/.makepkg.conf`` with ``makepkg.conf`` overrides if required (at least you might want to set ``PACKAGER``):
.. code-block:: shell
echo 'PACKAGER="John Doe <john@doe.com>"' | sudo -u ahriman tee -a /var/lib/ahriman/.makepkg.conf
#.
Configure build tools (it is required for correct dependency management system):
#.
Create build command, e.g. ``ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build`` (you can choose any name for command, basically it should be ``{name}-{arch}-build``).
#.
Create configuration file, e.g. ``cp /usr/share/devtools/pacman-{extra,ahriman}.conf`` (same as previous ``pacman-{name}.conf``).
#.
Change configuration file, add your own repository, add multilib repository etc;
#.
Set ``build_command`` option to point to your command.
#.
Configure ``/etc/sudoers.d/ahriman`` to allow running command without a password.
.. code-block:: shell
ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build
cp /usr/share/devtools/pacman-{extra,ahriman}.conf
echo '[multilib]' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo 'Include = /etc/pacman.d/mirrorlist' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo '[aur-clone]' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo 'SigLevel = Optional TrustAll' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/share/devtools/pacman-ahriman.conf
echo '[build]' | tee -a /etc/ahriman.ini.d/build.ini
echo 'build_command = ahriman-x86_64-build' | tee -a /etc/ahriman.ini.d/build.ini
echo 'Cmnd_Alias CARCHBUILD_CMD = /usr/local/bin/ahriman-x86_64-build *' | tee -a /etc/sudoers.d/ahriman
echo 'ahriman ALL=(ALL) NOPASSWD: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
chmod 400 /etc/sudoers.d/ahriman
#.
Start and enable ``ahriman@.timer`` via ``systemctl``:
.. code-block:: shell
systemctl enable --now ahriman@x86_64.timer
#.
Start and enable status page:
.. code-block:: shell
systemctl enable --now ahriman-web@x86_64
#.
Add packages by using ``ahriman package-add {package}`` command:
.. code-block:: shell
sudo -u ahriman ahriman -a x86_64 package-add ahriman --now

62
docs/triggers.rst Normal file
View File

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

View File

@ -1,7 +1,7 @@
# Maintainer: Evgeniy Alekseev
pkgname='ahriman'
pkgver=2.0.0rc5
pkgver=2.0.0rc11
pkgrel=1
pkgdesc="ArcH Linux ReposItory MANager"
arch=('any')
@ -38,7 +38,7 @@ build() {
package() {
cd "$pkgname"
python -m installer --destdir="$pkgdir" dist/*.whl
python -m installer --destdir="$pkgdir" "dist/$pkgname-$pkgver-py3-none-any.whl"
# python-installer actually thinks that you cannot just copy files to root
# thus we need to copy them manually

View File

@ -4,7 +4,6 @@ logging = ahriman.ini.d/logging.ini
database = /var/lib/ahriman/ahriman.db
[alpm]
aur_url = https://aur.archlinux.org
database = /var/lib/pacman
repositories = core extra community multilib
root = /
@ -22,6 +21,7 @@ build_command = extra-x86_64-build
ignore_packages =
makechrootpkg_flags =
makepkg_flags = --nocolor
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
[repository]
name = aur-clone

View File

@ -74,7 +74,7 @@
{% for package in packages %}
<tr data-package-base="{{ package.base }}">
<td data-checkbox="true"></td>
<td><a href="{{ package.web_url }}" title="{{ package.base }}">{{ package.base }}</a></td>
<td>{% if package.web_url is not none %}<a href="{{ package.web_url }}" title="{{ package.base }}">{{ package.base }}</a>{% else %}{{ package.base }}{% endif %}</td>
<td>{{ package.version }}</td>
<td>{{ package.packages|join("<br>"|safe) }}</td>
<td>{{ package.groups|join("<br>"|safe) }}</td>

View File

@ -85,6 +85,9 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
<li><a class="nav-link" href="{{ homepage }}" title="homepage">homepage</a></li>
{% endif %}
</ul>
<ul class="nav">
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources">ahriman</a></li>
</ul>
</footer>
</div>

View File

@ -1,5 +1,4 @@
{#simplified version of full report#}
<b>{{ repository }} update</b>
{% for package in packages %}
<a href="{{ link_path }}/{{ package.filename }}">{{ package.name }}</a> {{ package.version }} at {{ package.build_date }}
{% endfor %}
<a href="{{ link_path }}/{{ package.filename }}">{{ package.name }}</a> {{ package.version }}{% endfor %}

View File

@ -31,7 +31,6 @@ setup(
install_requires=[
"inflection",
"passlib",
"pyalpm",
"requests",
"srcinfo",
],
@ -93,6 +92,19 @@ setup(
"mypy",
"pylint",
],
"docs": [
"Sphinx",
"argparse-manpage",
"pydeps",
"sphinx-argparse",
"sphinxcontrib-napoleon",
],
# FIXME technically this dependency is required, but in some cases we do not have access to
# the libalpm which is required in order to install the package. Thus in case if we do not
# really need to run the application we can move it to "optional" dependencies
"pacman": [
"pyalpm",
],
"s3": [
"boto3",
],

View File

@ -22,7 +22,7 @@ import sys
import tempfile
from pathlib import Path
from typing import TypeVar
from typing import List, TypeVar
from ahriman import version
from ahriman.application import handlers
@ -33,6 +33,9 @@ from ahriman.models.sign_settings import SignSettings
from ahriman.models.user_access import UserAccess
__all__: List[str] = []
# this workaround is for several things
# firstly python devs don't think that is it error and asking you for workarounds https://bugs.python.org/issue41592
# secondly linters don't like when you are importing private members
@ -43,8 +46,12 @@ SubParserAction = TypeVar("SubParserAction", bound="argparse._SubParsersAction[a
def _formatter(prog: str) -> argparse.HelpFormatter:
"""
formatter for the help message
:param prog: application name
:return: formatter used by default
Args:
prog(str): application name
Returns:
argparse.HelpFormatter: formatter used by default
"""
return argparse.ArgumentDefaultsHelpFormatter(prog, width=120)
@ -52,7 +59,9 @@ def _formatter(prog: str) -> argparse.HelpFormatter:
def _parser() -> argparse.ArgumentParser:
"""
command line parser generator
:return: command line parser for the application
Returns:
argparse.ArgumentParser: command line parser for the application
"""
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH Linux ReposItory MANager",
epilog="Argument list can also be read from file by using @ prefix.",
@ -83,17 +92,17 @@ def _parser() -> argparse.ArgumentParser:
_set_patch_add_parser(subparsers)
_set_patch_list_parser(subparsers)
_set_patch_remove_parser(subparsers)
_set_repo_backup_parser(subparsers)
_set_repo_check_parser(subparsers)
_set_repo_clean_parser(subparsers)
_set_repo_config_parser(subparsers)
_set_repo_rebuild_parser(subparsers)
_set_repo_remove_unknown_parser(subparsers)
_set_repo_report_parser(subparsers)
_set_repo_restore_parser(subparsers)
_set_repo_setup_parser(subparsers)
_set_repo_sign_parser(subparsers)
_set_repo_status_update_parser(subparsers)
_set_repo_sync_parser(subparsers)
_set_repo_triggers_parser(subparsers)
_set_repo_update_parser(subparsers)
_set_user_add_parser(subparsers)
_set_user_list_parser(subparsers)
@ -106,8 +115,12 @@ def _parser() -> argparse.ArgumentParser:
def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for AUR search subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("aur-search", aliases=["search"], help="search for package",
description="search for package in AUR using API", formatter_class=_formatter)
@ -125,8 +138,12 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for listing help subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("help", help="show help message",
description="show help message for application or command and exit",
@ -140,8 +157,12 @@ def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for listing unsafe commands
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
description="list unsafe commands as defined in default args", formatter_class=_formatter)
@ -155,8 +176,12 @@ def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.Argument
def _set_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for key import subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("key-import", help="import PGP key",
description="import PGP key from public sources to the repository user",
@ -174,8 +199,12 @@ def _set_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package addition subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-add", aliases=["add", "package-update"], help="add package",
description="add existing or new package to the build queue",
@ -203,8 +232,12 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package removal subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-remove", aliases=["remove"], help="remove package",
description="remove package from the repository", formatter_class=_formatter)
@ -216,8 +249,12 @@ def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser
def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package status subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-status", aliases=["status"], help="get package status",
description="request status of the package",
@ -236,8 +273,12 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
def _set_package_status_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package status remove subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-status-remove", help="remove package status",
description="remove the package from the status page",
@ -253,8 +294,12 @@ def _set_package_status_remove_parser(root: SubParserAction) -> argparse.Argumen
def _set_package_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package status update subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-status-update", aliases=["status-update"], help="update package status",
description="update package status on the status page", formatter_class=_formatter)
@ -271,13 +316,17 @@ def _set_package_status_update_parser(root: SubParserAction) -> argparse.Argumen
def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for new patch subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("patch-add", help="add patch set", description="create or update source patches",
epilog="In order to add a patch set for the package you will need to clone "
"the AUR package manually, add required changes (e.g. external patches, "
"edit PKGBUILD) and run command, e.g. `ahriman patch path/to/directory`. "
"edit PKGBUILD) and run command, e.g. ``ahriman patch path/to/directory``. "
"By default it tracks *.patch and *.diff files, but this behavior can be changed "
"by using --track option",
formatter_class=_formatter)
@ -291,8 +340,12 @@ def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for list patches subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("patch-list", help="list patch sets",
description="list available patches for the package", formatter_class=_formatter)
@ -305,8 +358,12 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for remove patches subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("patch-remove", help="remove patch set", description="remove patches for the package",
formatter_class=_formatter)
@ -315,11 +372,32 @@ def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser
def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository backup subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-backup", help="backup repository data",
description="backup settings and database", formatter_class=_formatter)
parser.add_argument("path", help="path of the output archive", type=Path)
parser.set_defaults(handler=handlers.Backup, architecture=[""], lock=None, no_report=True, unsafe=True)
return parser
def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository check subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-check", aliases=["check"], help="check for updates",
description="check for packages updates. Same as update --dry-run --no-manual",
@ -334,8 +412,12 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository clean subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-clean", aliases=["clean"], help="clean local caches",
description="remove local caches",
@ -354,8 +436,12 @@ def _set_repo_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_config_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for config subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-config", aliases=["config"], help="dump configuration",
description="dump configuration for the specified architecture",
@ -367,14 +453,24 @@ def _set_repo_config_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository rebuild subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository",
description="force rebuild whole repository", formatter_class=_formatter)
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append")
parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself",
action="store_true")
parser.add_argument("--from-database",
help="read packages from database instead of filesystem. This feature in particular is "
"required in case if you would like to restore repository from another repository "
"instance. Note however that in order to restore packages you need to have original "
"ahriman instance run with web service and have run repo-update at least once.",
action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.set_defaults(handler=handlers.Rebuild)
return parser
@ -383,8 +479,12 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for remove unknown packages subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-remove-unknown", aliases=["remove-unknown"], help="remove unknown packages",
description="remove packages which are missing in AUR and do not have local PKGBUILDs",
@ -395,41 +495,33 @@ def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentP
return parser
def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for report subcommand
:param root: subparsers for the commands
:return: created argument parser
"""
parser = root.add_parser("repo-report", aliases=["report"], help="generate report",
description="generate repository report according to current settings",
epilog="Create and/or update repository report as configured.",
formatter_class=_formatter)
parser.add_argument("target", help="target to generate report", nargs="*")
parser.set_defaults(handler=handlers.Report)
return parser
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package addition subcommand
:param root: subparsers for the commands
:return: created argument parser
add parser for repository restore subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-restore", aliases=["restore"], help="restore repository",
description="restore repository from database file", formatter_class=_formatter)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
parser.set_defaults(handler=handlers.Add, package=None, source=PackageSource.AUR)
parser = root.add_parser("repo-restore", help="restore repository data",
description="restore settings and database", formatter_class=_formatter)
parser.add_argument("path", help="path of the input archive", type=Path)
parser.add_argument("-o", "--output", help="root path of the extracted files", type=Path, default=Path("/"))
parser.set_defaults(handler=handlers.Restore, architecture=[""], lock=None, no_report=True, unsafe=True)
return parser
def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for setup subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-setup", aliases=["init", "repo-init", "setup"], help="initial service configuration",
description="create initial service configuration, requires root",
@ -453,8 +545,12 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for sign subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-sign", aliases=["sign"], help="sign packages",
description="(re-)sign packages and repository database according to current settings",
@ -468,8 +564,12 @@ def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository status update subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-status-update", help="update repository status",
description="update repository status on the status page", formatter_class=_formatter)
@ -480,26 +580,32 @@ def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentPa
return parser
def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository sync subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-sync", aliases=["sync"], help="sync repository",
description="sync repository files to remote server according to current settings",
epilog="Synchronize the repository to remote services as configured.",
parser = root.add_parser("repo-triggers", help="run triggers",
description="run triggers on empty build result as configured by settings",
formatter_class=_formatter)
parser.add_argument("target", help="target to sync", nargs="*")
parser.set_defaults(handler=handlers.Sync)
parser.set_defaults(handler=handlers.Triggers)
return parser
def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository update subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-update", aliases=["update"], help="update packages",
description="check for packages updates and run build process if requested",
@ -518,8 +624,12 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for create user subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("user-add", help="create or update user",
description="update user for web services with the given password and role. "
@ -532,7 +642,7 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-r", "--role", help="user access level",
type=UserAccess, choices=UserAccess, default=UserAccess.Read)
parser.add_argument("-s", "--secure", help="set file permissions to user-only", action="store_true")
parser.set_defaults(handler=handlers.User, action=Action.Update, architecture=[""], lock=None, no_report=True,
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, no_report=True,
quiet=True, unsafe=True)
return parser
@ -540,8 +650,12 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for user list subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("user-list", help="user known users and their access",
description="list users from the user mapping and their roles",
@ -549,7 +663,7 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("username", help="filter users by username", nargs="?")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-r", "--role", help="filter users by role", type=UserAccess, choices=UserAccess)
parser.set_defaults(handler=handlers.User, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec
password="", quiet=True, unsafe=True)
return parser
@ -557,15 +671,19 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for user removal subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("user-remove", help="remove user",
description="remove user from the user mapping and update the configuration",
formatter_class=_formatter)
parser.add_argument("username", help="username for web service")
parser.add_argument("-s", "--secure", help="set file permissions to user-only", action="store_true")
parser.set_defaults(handler=handlers.User, action=Action.Remove, architecture=[""], lock=None, no_report=True, # nosec
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, no_report=True, # nosec
password="", quiet=True, unsafe=True)
return parser
@ -573,8 +691,12 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for web subcommand
:param root: subparsers for the commands
:return: created argument parser
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("web", help="web server", description="start web server", formatter_class=_formatter)
parser.set_defaults(handler=handlers.Web, lock=None, no_report=True, parser=_parser)

View File

@ -19,28 +19,51 @@
#
from typing import Set
from ahriman.application.application.packages import Packages
from ahriman.application.application.repository import Repository
from ahriman.application.application.application_packages import ApplicationPackages
from ahriman.application.application.application_repository import ApplicationRepository
from ahriman.models.result import Result
class Application(Packages, Repository):
class Application(ApplicationPackages, ApplicationRepository):
"""
base application class
Examples:
This class groups ``Repository`` methods into specific method which process all supposed actions caused by
underlying action. E.g.::
>>> from ahriman.core.configuration import Configuration
>>> from ahriman.models.package_source import PackageSource
>>>
>>> configuration = Configuration()
>>> application = Application("x86_64", configuration, no_report=False, unsafe=False)
>>> # add packages to build queue
>>> application.add(["ahriman"], PackageSource.AUR, without_dependencies=False)
>>>
>>> # check for updates
>>> updates = application.updates([], no_aur=False, no_local=False, no_manual=False, no_vcs=False, log_fn=print)
>>> # updates for specified packages
>>> application.update(updates)
In case if specific actions or their order are required, the direct access to ``Repository`` must
be used instead.
"""
def _finalize(self, result: Result) -> None:
"""
generate report and sync to remote server
:param result: build result
Args:
result(Result): build result
"""
self.report([], result)
self.sync([], result.success)
self.repository.process_triggers(result)
def _known_packages(self) -> Set[str]:
"""
load packages from repository and pacman repositories
:return: list of known packages
Returns:
Set[str]: list of known packages
"""
known_packages: Set[str] = set()
# local set

View File

@ -23,7 +23,7 @@ import shutil
from pathlib import Path
from typing import Any, Iterable, Set
from ahriman.application.application.properties import Properties
from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.core.build_tools.sources import Sources
from ahriman.core.util import package_like, tmpdir
from ahriman.models.package import Package
@ -31,7 +31,7 @@ from ahriman.models.package_source import PackageSource
from ahriman.models.result import Result
class Packages(Properties):
class ApplicationPackages(ApplicationProperties):
"""
package control class
"""
@ -39,21 +39,33 @@ class Packages(Properties):
def _finalize(self, result: Result) -> None:
"""
generate report and sync to remote server
:param result: build result
Args:
result(Result): build result
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def _known_packages(self) -> Set[str]:
"""
load packages from repository and pacman repositories
:return: list of known packages
Returns:
Set[str]: list of known packages
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def _add_archive(self, source: str, *_: Any) -> None:
"""
add package from archive
:param source: path to package archive
Args:
source(str): path to package archive
"""
local_path = Path(source)
dst = self.repository.paths.packages / local_path.name
@ -62,37 +74,45 @@ class Packages(Properties):
def _add_aur(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
"""
add package from AUR
:param source: package base name
:param known_packages: list of packages which are known by the service
:param without_dependencies: if set, dependency check will be disabled
Args:
source(str): package base name
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
package = Package.load(source, PackageSource.AUR, self.repository.pacman, self.repository.aur_url)
package = Package.from_aur(source, self.repository.pacman)
self.database.build_queue_insert(package)
self.database.remote_update(package)
with tmpdir() as local_path:
Sources.load(local_path, package.git_url, self.database.patches_get(package.base))
self._process_dependencies(local_path, known_packages, without_dependencies)
with tmpdir() as local_dir:
Sources.load(local_dir, package, self.database.patches_get(package.base), self.repository.paths)
self._process_dependencies(local_dir, known_packages, without_dependencies)
def _add_directory(self, source: str, *_: Any) -> None:
"""
add packages from directory
:param source: path to local directory
Args:
source(str): path to local directory
"""
local_path = Path(source)
for full_path in filter(package_like, local_path.iterdir()):
local_dir = Path(source)
for full_path in filter(package_like, local_dir.iterdir()):
self._add_archive(str(full_path))
def _add_local(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
"""
add package from local PKGBUILDs
:param source: path to directory with local source files
:param known_packages: list of packages which are known by the service
:param without_dependencies: if set, dependency check will be disabled
Args:
source(str): path to directory with local source files
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
package = Package.load(source, PackageSource.Local, self.repository.pacman, self.repository.aur_url)
source_dir = Path(source)
package = Package.from_build(source_dir)
cache_dir = self.repository.paths.cache_for(package.base)
shutil.copytree(Path(source), cache_dir) # copy package to store in caches
shutil.copytree(source_dir, cache_dir) # copy package to store in caches
Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
self.database.build_queue_insert(package)
@ -102,7 +122,9 @@ class Packages(Properties):
def _add_remote(self, source: str, *_: Any) -> None:
"""
add package from remote sources (e.g. HTTP)
:param remote_url: remote URL to the package archive
Args:
source(str): remote URL of the package archive
"""
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
response = requests.get(source, stream=True)
@ -112,25 +134,41 @@ class Packages(Properties):
for chunk in response.iter_content(chunk_size=1024):
local_file.write(chunk)
def _process_dependencies(self, local_path: Path, known_packages: Set[str], without_dependencies: bool) -> None:
def _add_repository(self, source: str, *_: Any) -> None:
"""
add package from official repository
Args:
source(str): package base name
"""
package = Package.from_official(source, self.repository.pacman)
self.database.build_queue_insert(package)
self.database.remote_update(package)
# repository packages must not depend on unknown packages, thus we are not going to process dependencies
def _process_dependencies(self, local_dir: Path, known_packages: Set[str], without_dependencies: bool) -> None:
"""
process package dependencies
:param local_path: path to local package sources (i.e. cloned AUR repository)
:param known_packages: list of packages which are known by the service
:param without_dependencies: if set, dependency check will be disabled
Args:
local_dir(Path): path to local package sources (i.e. cloned AUR repository)
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
if without_dependencies:
return
dependencies = Package.dependencies(local_path)
dependencies = Package.dependencies(local_dir)
self.add(dependencies.difference(known_packages), PackageSource.AUR, without_dependencies)
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:
"""
add packages for the next build
:param names: list of package bases to add
:param source: package source to add
:param without_dependencies: if set, dependency check will be disabled
Args:
names(Iterable[str]): list of package bases to add
source(PackageSource): package source to add
without_dependencies(bool): if set, dependency check will be disabled
"""
known_packages = self._known_packages() # speedup dependencies processing
@ -142,7 +180,9 @@ class Packages(Properties):
def remove(self, names: Iterable[str]) -> None:
"""
remove packages from repository
:param names: list of packages (either base or name) to remove
Args:
names(Iterable[str]): list of packages (either base or name) to remove
"""
self.repository.process_remove(names)
self._finalize(Result())

View File

@ -20,27 +20,31 @@
import logging
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository
class Properties:
class ApplicationProperties:
"""
application base properties class
:ivar architecture: repository architecture
:ivar configuration: configuration instance
:ivar database: database instance
:ivar logger: application logger
:ivar repository: repository instance
Attributes:
architecture(str): repository architecture
configuration(Configuration): configuration instance
database(SQLite): database instance
logger(logging.Logger): application logger
repository(Repository): repository instance
"""
def __init__(self, architecture: str, configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
default constructor
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
self.logger = logging.getLogger("root")
self.configuration = configuration

View File

@ -22,15 +22,15 @@ import shutil
from pathlib import Path
from typing import Callable, Iterable, List
from ahriman.application.application.properties import Properties
from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.core.build_tools.sources import Sources
from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters import UpdatePrinter
from ahriman.core.tree import Tree
from ahriman.models.package import Package
from ahriman.models.result import Result
class Repository(Properties):
class ApplicationRepository(ApplicationProperties):
"""
repository control class
"""
@ -38,17 +38,24 @@ class Repository(Properties):
def _finalize(self, result: Result) -> None:
"""
generate report and sync to remote server
:param result: build result
Args:
result(Result): build result
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def clean(self, cache: bool, chroot: bool, manual: bool, packages: bool) -> None:
"""
run all clean methods. Warning: some functions might not be available under non-root
:param cache: clear directory with package caches
:param chroot: clear build chroot
:param manual: clear directory with manually added packages
:param packages: clear directory with built packages
Args:
cache(bool): clear directory with package caches
chroot(bool): clear build chroot
manual(bool): clear directory with manually added packages
packages(bool): clear directory with built packages
"""
if cache:
self.repository.clear_cache()
@ -59,19 +66,12 @@ class Repository(Properties):
if packages:
self.repository.clear_packages()
def report(self, target: Iterable[str], result: Result) -> None:
"""
generate report
:param target: list of targets to run (e.g. html)
:param result: build result
"""
targets = target or None
self.repository.process_report(targets, result)
def sign(self, packages: Iterable[str]) -> None:
"""
sign packages and repository
:param packages: only sign specified packages
Args:
packages(Iterable[str]): only sign specified packages
"""
# copy to prebuilt directory
for package in self.repository.packages():
@ -91,19 +91,12 @@ class Repository(Properties):
self.repository.sign.process_sign_repository(self.repository.repo.repo_path)
self._finalize(Result())
def sync(self, target: Iterable[str], built_packages: Iterable[Package]) -> None:
"""
sync to remote server
:param target: list of targets to run (e.g. s3)
:param built_packages: list of packages which has just been built
"""
targets = target or None
self.repository.process_sync(targets, built_packages)
def unknown(self) -> List[str]:
"""
get packages which were not found in AUR
:return: unknown package archive list
Returns:
List[str]: unknown package archive list
"""
def has_local(probe: Package) -> bool:
cache_dir = self.repository.paths.cache_for(probe.base)
@ -113,14 +106,14 @@ class Repository(Properties):
packages: List[str] = []
for single in probe.packages:
try:
_ = Package.from_aur(single, probe.aur_url)
_ = Package.from_aur(single, self.repository.pacman)
except Exception:
packages.append(single)
return packages
def unknown_local(probe: Package) -> List[str]:
cache_dir = self.repository.paths.cache_for(probe.base)
local = Package.from_build(cache_dir, probe.aur_url)
local = Package.from_build(cache_dir)
packages = set(probe.packages.keys()).difference(local.packages.keys())
return list(packages)
@ -135,7 +128,12 @@ class Repository(Properties):
def update(self, updates: Iterable[Package]) -> Result:
"""
run package updates
:param updates: list of packages to update
Args:
updates(Iterable[Package]): list of packages to update
Returns:
Result: update result
"""
def process_update(paths: Iterable[Path], result: Result) -> None:
if not paths:
@ -149,7 +147,7 @@ class Repository(Properties):
process_update(packages, build_result)
# process manual packages
tree = Tree.load(updates, self.database)
tree = Tree.load(updates, self.repository.paths, self.database)
for num, level in enumerate(tree.levels()):
self.logger.info("processing level #%i %s", num, [package.base for package in level])
build_result = self.repository.process_build(level)
@ -162,13 +160,17 @@ class Repository(Properties):
log_fn: Callable[[str], None]) -> List[Package]:
"""
get list of packages to run update process
:param filter_packages: do not check every package just specified in the list
:param no_aur: do not check for aur updates
:param no_local: do not check local packages for updates
:param no_manual: do not check for manual updates
:param no_vcs: do not check VCS packages
:param log_fn: logger function to log updates
:return: list of out-of-dated packages
Args:
filter_packages(Iterable[str]): do not check every package just specified in the list
no_aur(bool): do not check for aur updates
no_local(bool): do not check local packages for updates
no_manual(bool): do not check for manual updates
no_vcs(bool): do not check VCS packages
log_fn(Callable[[str], None]): logger function to log updates
Returns:
List[Package]: list of out-of-dated packages
"""
updates = {}

View File

@ -20,6 +20,7 @@
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers.add import Add
from ahriman.application.handlers.backup import Backup
from ahriman.application.handlers.clean import Clean
from ahriman.application.handlers.dump import Dump
from ahriman.application.handlers.help import Help
@ -28,14 +29,14 @@ from ahriman.application.handlers.patch import Patch
from ahriman.application.handlers.rebuild import Rebuild
from ahriman.application.handlers.remove import Remove
from ahriman.application.handlers.remove_unknown import RemoveUnknown
from ahriman.application.handlers.report import Report
from ahriman.application.handlers.restore import Restore
from ahriman.application.handlers.search import Search
from ahriman.application.handlers.setup import Setup
from ahriman.application.handlers.sign import Sign
from ahriman.application.handlers.status import Status
from ahriman.application.handlers.status_update import StatusUpdate
from ahriman.application.handlers.sync import Sync
from ahriman.application.handlers.triggers import Triggers
from ahriman.application.handlers.unsafe_commands import UnsafeCommands
from ahriman.application.handlers.update import Update
from ahriman.application.handlers.user import User
from ahriman.application.handlers.users import Users
from ahriman.application.handlers.web import Web

View File

@ -19,10 +19,10 @@
#
import argparse
from typing import List, Type
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
@ -36,27 +36,19 @@ class Add(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
application = Application(architecture, configuration, no_report, unsafe)
packages = Add.extract_packages(application) if args.package is None else args.package
application.add(packages, args.source, args.without_dependencies)
application.add(args.package, args.source, args.without_dependencies)
if not args.now:
return
packages = application.updates(packages, True, True, False, True, application.logger.info)
packages = application.updates(args.package, True, True, False, True, application.logger.info)
result = application.update(packages)
Add.check_if_empty(args.exit_code, result.is_empty)
@staticmethod
def extract_packages(application: Application) -> List[str]:
"""
extract packages from database file
:param application: application instance
:return: list of packages which were stored in database
"""
return [package.base for (package, _) in application.database.packages_get()]

View File

@ -0,0 +1,86 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
import pwd
from pathlib import Path
from tarfile import TarFile
from typing import Set, Type
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
class Backup(Handler):
"""
backup packages handler
"""
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
backup_paths = Backup.get_paths(configuration)
with TarFile(args.path, mode="w") as archive: # well we don't actually use compression
for backup_path in backup_paths:
archive.add(backup_path)
@staticmethod
def get_paths(configuration: Configuration) -> Set[Path]:
"""
extract paths to backup
Args:
configuration(Configuration): configuration instance
Returns:
Set[Path]: map of the filesystem paths
"""
paths = set(configuration.include.glob("*.ini"))
root, _ = configuration.check_loaded()
paths.add(root) # the configuration itself
paths.add(SQLite.database_path(configuration)) # database
# local caches
repository_paths = configuration.repository_paths
if repository_paths.cache.is_dir():
paths.add(repository_paths.cache)
# gnupg home with imported keys
uid, _ = repository_paths.root_owner
system_user = pwd.getpwuid(uid)
gnupg_home = Path(system_user.pw_dir) / ".gnupg"
if gnupg_home.is_dir():
paths.add(gnupg_home)
return paths

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
@ -36,11 +36,13 @@ class Clean(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).clean(
args.cache, args.chroot, args.manual, args.packages)

View File

@ -21,9 +21,9 @@ import argparse
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
from ahriman.core.formatters import ConfigurationPrinter
class Dump(Handler):
@ -38,11 +38,13 @@ class Dump(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
dump = configuration.dump()
for section, values in sorted(dump.items()):

View File

@ -34,8 +34,17 @@ from ahriman.models.repository_paths import RepositoryPaths
class Handler:
"""
base handler class for command callbacks
:cvar ALLOW_AUTO_ARCHITECTURE_RUN: allow to define architecture from existing repositories
:cvar ALLOW_MULTI_ARCHITECTURE_RUN: allow to run with multiple architectures
Attributes:
ALLOW_AUTO_ARCHITECTURE_RUN(bool): (class attribute) allow defining architecture from existing repositories
ALLOW_MULTI_ARCHITECTURE_RUN(bool): (class attribute) allow running with multiple architectures
Examples:
Wrapper for all command line actions, though each derived class implements ``run`` method, it usually must not
be called directly. The recommended way is to call ``execute`` class method, e.g.::
>>> from ahriman.application.handlers import Add
>>> Add.execute(args)
"""
ALLOW_AUTO_ARCHITECTURE_RUN = True
@ -45,8 +54,15 @@ class Handler:
def architectures_extract(cls: Type[Handler], args: argparse.Namespace) -> List[str]:
"""
get known architectures
:param args: command line args
:return: list of architectures for which tree is created
Args:
args(argparse.Namespace): command line args
Returns:
List[str]: list of architectures for which tree is created
Raises:
MissingArchitecture: if no architecture set and automatic detection is not allowed or failed
"""
if not cls.ALLOW_AUTO_ARCHITECTURE_RUN and args.architecture is None:
# for some parsers (e.g. config) we need to run with specific architecture
@ -55,10 +71,10 @@ class Handler:
if args.architecture: # architecture is specified explicitly
return sorted(set(args.architecture))
config = Configuration()
config.load(args.configuration)
configuration = Configuration()
configuration.load(args.configuration)
# wtf???
root = config.getpath("repository", "root") # pylint: disable=assignment-from-no-return
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
architectures = RepositoryPaths.known_architectures(root)
if not architectures: # well we did not find anything
@ -69,9 +85,13 @@ class Handler:
def call(cls: Type[Handler], args: argparse.Namespace, architecture: str) -> bool:
"""
additional function to wrap all calls for multiprocessing library
:param args: command line args
:param architecture: repository architecture
:return: True on success, False otherwise
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
Returns:
bool: True on success, False otherwise
"""
try:
configuration = Configuration.from_path(args.configuration, architecture, args.quiet)
@ -89,8 +109,15 @@ class Handler:
def execute(cls: Type[Handler], args: argparse.Namespace) -> int:
"""
execute function for all aru
:param args: command line args
:return: 0 on success, 1 otherwise
Args:
args(argparse.Namespace): command line args
Returns:
int: 0 on success, 1 otherwise
Raises:
MultipleArchitectures: if more than one architecture supplied and no multi architecture supported
"""
architectures = cls.architectures_extract(args)
@ -112,11 +139,16 @@ class Handler:
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
@ -124,8 +156,13 @@ class Handler:
def check_if_empty(enabled: bool, predicate: bool) -> None:
"""
check condition and flag and raise ExitCode exception in case if it is enabled and condition match
:param enabled: if False no check will be performed
:param predicate: indicates condition on which exception should be thrown
Args:
enabled(bool): if False no check will be performed
predicate(bool): indicates condition on which exception should be thrown
Raises:
ExitCode: if result is empty and check is enabled
"""
if enabled and predicate:
raise ExitCode()

View File

@ -21,7 +21,7 @@ import argparse
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
@ -37,11 +37,13 @@ class Help(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
parser: argparse.ArgumentParser = args.parser()
if args.command is None:

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
@ -38,11 +38,13 @@ class KeyImport(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).repository.sign.key_import(
args.key_server, args.key)

View File

@ -23,13 +23,12 @@ from pathlib import Path
from typing import List, Optional, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.action import Action
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
class Patch(Handler):
@ -42,11 +41,13 @@ class Patch(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
application = Application(architecture, configuration, no_report, unsafe)
@ -55,28 +56,31 @@ class Patch(Handler):
elif args.action == Action.Remove:
Patch.patch_set_remove(application, args.package)
elif args.action == Action.Update:
Patch.patch_set_create(application, args.package, args.track)
Patch.patch_set_create(application, Path(args.package), args.track)
@staticmethod
def patch_set_create(application: Application, sources_dir: str, track: List[str]) -> None:
def patch_set_create(application: Application, sources_dir: Path, track: List[str]) -> None:
"""
create patch set for the package base
:param application: application instance
:param sources_dir: path to directory with the package sources
:param track: track files which match the glob before creating the patch
Args:
application(Application): application instance
sources_dir(Path): path to directory with the package sources
track(List[str]): track files which match the glob before creating the patch
"""
package = Package.load(sources_dir, PackageSource.Local, application.repository.pacman,
application.repository.aur_url)
patch = Sources.patch_create(Path(sources_dir), *track)
package = Package.from_build(sources_dir)
patch = Sources.patch_create(sources_dir, *track)
application.database.patches_insert(package.base, patch)
@staticmethod
def patch_set_list(application: Application, package_base: Optional[str], exit_code: bool) -> None:
"""
list patches available for the package base
:param application: application instance
:param package_base: package base
:param exit_code: raise ExitCode on empty search result
Args:
application(Application): application instance
package_base(Optional[str]): package base
exit_code(bool): exit with error on empty search result
"""
patches = application.database.patches_list(package_base)
Patch.check_if_empty(exit_code, not patches)
@ -89,7 +93,9 @@ class Patch(Handler):
def patch_set_remove(application: Application, package_base: str) -> None:
"""
remove patch set for the package base
:param application: application instance
:param package_base: package base
Args:
application(Application): application instance
package_base(str): package base
"""
application.database.patches_remove(package_base)

View File

@ -19,12 +19,13 @@
#
import argparse
from typing import Type
from typing import List, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters import UpdatePrinter
from ahriman.models.package import Package
class Rebuild(Handler):
@ -37,16 +38,21 @@ class Rebuild(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
depends_on = set(args.depends_on) if args.depends_on else None
application = Application(architecture, configuration, no_report, unsafe)
updates = application.repository.packages_depends_on(depends_on)
if args.from_database:
updates = Rebuild.extract_packages(application)
else:
updates = application.repository.packages_depends_on(depends_on)
Rebuild.check_if_empty(args.exit_code, not updates)
if args.dry_run:
@ -56,3 +62,16 @@ class Rebuild(Handler):
result = application.update(updates)
Rebuild.check_if_empty(args.exit_code, result.is_empty)
@staticmethod
def extract_packages(application: Application) -> List[Package]:
"""
extract packages from database file
Args:
application(Application): application instance
Returns:
List[Package]: list of packages which were stored in database
"""
return [package for (package, _) in application.database.packages_get()]

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
@ -36,10 +36,12 @@ class Remove(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).remove(args.package)

View File

@ -22,9 +22,9 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
class RemoveUnknown(Handler):
@ -37,11 +37,13 @@ class RemoveUnknown(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
application = Application(architecture, configuration, no_report, unsafe)
unknown_packages = application.unknown()

View File

@ -20,26 +20,31 @@
import argparse
from typing import Type
from tarfile import TarFile
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
class Sync(Handler):
class Restore(Handler):
"""
remove sync handler
restore packages handler
"""
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).sync(args.target, [])
with TarFile(args.path) as archive:
archive.extractall(path=args.output)

View File

@ -22,19 +22,21 @@ import argparse
from dataclasses import fields
from typing import Callable, Iterable, List, Tuple, Type
from ahriman.application.handlers.handler import Handler
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official
from ahriman.application.application import Application
from ahriman.application.handlers import Handler
from ahriman.core.alpm.remote import AUR, Official
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InvalidOption
from ahriman.core.formatters.aur_printer import AurPrinter
from ahriman.core.formatters import AurPrinter
from ahriman.models.aur_package import AURPackage
class Search(Handler):
"""
packages search handler
:cvar SORT_FIELDS: allowed fields to sort the package list
Attributes:
SORT_FIELDS(Set[str]): (class attribute) allowed fields to sort the package list
"""
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@ -45,14 +47,18 @@ class Search(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
official_packages_list = Official.multisearch(*args.search)
aur_packages_list = AUR.multisearch(*args.search)
application = Application(architecture, configuration, no_report, unsafe)
official_packages_list = Official.multisearch(*args.search, pacman=application.repository.pacman)
aur_packages_list = AUR.multisearch(*args.search, pacman=application.repository.pacman)
Search.check_if_empty(args.exit_code, not official_packages_list and not aur_packages_list)
for packages_list in (official_packages_list, aur_packages_list):
@ -64,9 +70,16 @@ class Search(Handler):
def sort(packages: Iterable[AURPackage], sort_by: str) -> List[AURPackage]:
"""
sort package list by specified field
:param packages: packages list to sort
:param sort_by: AUR package field name to sort by
:return: sorted list for packages
Args:
packages(Iterable[AURPackage]): packages list to sort
sort_by(str): AUR package field name to sort by
Returns:
List[AURPackage]: sorted list for packages
Raises:
InvalidOption: if search fields is not in list of allowed ones
"""
if sort_by not in Search.SORT_FIELDS:
raise InvalidOption(sort_by)

View File

@ -23,7 +23,7 @@ from pathlib import Path
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths
@ -31,10 +31,12 @@ from ahriman.models.repository_paths import RepositoryPaths
class Setup(Handler):
"""
setup handler
:cvar ARCHBUILD_COMMAND_PATH: default devtools command
:cvar BIN_DIR_PATH: directory for custom binaries
:cvar MIRRORLIST_PATH: path to pacman default mirrorlist (used by multilib repository)
:cvar SUDOERS_PATH: path to sudoers.d include configuration
Attributes:
ARCHBUILD_COMMAND_PATH(Path): (class attribute) default devtools command
BIN_DIR_PATH(Path): (class attribute) directory for custom binaries
MIRRORLIST_PATH(Path): (class attribute) path to pacman default mirrorlist (used by multilib repository)
SUDOERS_PATH(Path): (class attribute) path to sudoers.d include configuration
"""
ALLOW_AUTO_ARCHITECTURE_RUN = False
@ -49,11 +51,13 @@ class Setup(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include)
configuration.reload()
@ -72,9 +76,13 @@ class Setup(Handler):
def build_command(prefix: str, architecture: str) -> Path:
"""
generate build command name
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture
:return: valid devtools command name
Args:
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
Returns:
Path: valid devtools command name
"""
return Setup.BIN_DIR_PATH / f"{prefix}-{architecture}-build"
@ -83,10 +91,12 @@ class Setup(Handler):
include_path: Path) -> None:
"""
create service specific configuration
:param args: command line args
:param architecture: repository architecture
:param repository: repository name
:param include_path: path to directory with configuration includes
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
repository(str): repository name
include_path(Path): path to directory with configuration includes
"""
configuration = Configuration()
@ -113,13 +123,15 @@ class Setup(Handler):
def configuration_create_devtools(prefix: str, architecture: str, source: Path,
no_multilib: bool, repository: str, paths: RepositoryPaths) -> None:
"""
create configuration for devtools based on `source` configuration
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture
:param source: path to source configuration file
:param no_multilib: do not add multilib repository
:param repository: repository name
:param paths: repository paths instance
create configuration for devtools based on ``source`` configuration
Args:
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
source(Path): path to source configuration file
no_multilib(bool): do not add multilib repository
repository(str): repository name
paths(RepositoryPaths): repository paths instance
"""
configuration = Configuration()
# preserve case
@ -149,8 +161,10 @@ class Setup(Handler):
def configuration_create_makepkg(packager: str, paths: RepositoryPaths) -> None:
"""
create configuration for makepkg
:param packager: packager identifier (e.g. name, email)
:param paths: repository paths instance
Args:
packager(str): packager identifier (e.g. name, email)
paths(RepositoryPaths): repository paths instance
"""
(paths.root / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8")
@ -158,8 +172,10 @@ class Setup(Handler):
def configuration_create_sudo(prefix: str, architecture: str) -> None:
"""
create configuration to run build command with sudo without password
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture
Args:
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
"""
command = Setup.build_command(prefix, architecture)
Setup.SUDOERS_PATH.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n", encoding="utf8")
@ -169,8 +185,10 @@ class Setup(Handler):
def executable_create(prefix: str, architecture: str) -> None:
"""
create executable for the service
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture
Args:
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
"""
command = Setup.build_command(prefix, architecture)
command.unlink(missing_ok=True)

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
@ -36,10 +36,12 @@ class Sign(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).sign(args.package)

View File

@ -22,10 +22,9 @@ import argparse
from typing import Callable, Iterable, Tuple, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.package_printer import PackagePrinter
from ahriman.core.formatters.status_printer import StatusPrinter
from ahriman.core.formatters import PackagePrinter, StatusPrinter
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
@ -42,11 +41,13 @@ class Status(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
# we are using reporter here
client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.action import Action
@ -39,11 +39,13 @@ class StatusUpdate(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
# we are using reporter here
client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter

View File

@ -22,14 +22,14 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.result import Result
class Report(Handler):
class Triggers(Handler):
"""
generate report handler
triggers handlers
"""
@classmethod
@ -37,10 +37,12 @@ class Report(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).report(args.target, Result())
Application(architecture, configuration, no_report, unsafe).repository.process_triggers(Result())

View File

@ -22,10 +22,9 @@ import shlex
from typing import List, Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ExitCode
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
class UnsafeCommands(Handler):
@ -40,11 +39,13 @@ class UnsafeCommands(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
parser = args.parser()
unsafe_commands = UnsafeCommands.get_unsafe_commands(parser)
@ -58,20 +59,25 @@ class UnsafeCommands(Handler):
def check_unsafe(command: str, unsafe_commands: List[str], parser: argparse.ArgumentParser) -> None:
"""
check if command is unsafe
:param command: command to check
:param unsafe_commands: list of unsafe commands
:param parser: generated argument parser
Args:
command(str): command to check
unsafe_commands(List[str]): list of unsafe commands
parser(argparse.ArgumentParser): generated argument parser
"""
args = parser.parse_args(shlex.split(command))
if args.command in unsafe_commands:
raise ExitCode()
UnsafeCommands.check_if_empty(True, args.command in unsafe_commands)
@staticmethod
def get_unsafe_commands(parser: argparse.ArgumentParser) -> List[str]:
"""
extract unsafe commands from argument parser
:param parser: generated argument parser
:return: list of commands with default unsafe flag
Args:
parser(argparse.ArgumentParser): generated argument parser
Returns:
List[str]: list of commands with default unsafe flag
"""
# pylint: disable=protected-access
subparser = next(action for action in parser._actions if isinstance(action, argparse._SubParsersAction))

View File

@ -22,7 +22,7 @@ import argparse
from typing import Callable, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
@ -36,11 +36,13 @@ class Update(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
application = Application(architecture, configuration, no_report, unsafe)
packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
@ -56,9 +58,13 @@ class Update(Handler):
def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]:
"""
package updates log function
:param application: application instance
:param dry_run: do not perform update itself
:return: in case if dry_run is set it will return print, logger otherwise
Args:
application(Application): application instance
dry_run(bool): do not perform update itself
Returns:
Callable[[str], None]: in case if dry_run is set it will return print, logger otherwise
"""
def inner(line: str) -> None:
return print(line) if dry_run else application.logger.info(line)

View File

@ -23,15 +23,15 @@ import getpass
from pathlib import Path
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.formatters.user_printer import UserPrinter
from ahriman.core.database import SQLite
from ahriman.core.formatters import UserPrinter
from ahriman.models.action import Action
from ahriman.models.user import User as MUser
from ahriman.models.user import User
class User(Handler):
class Users(Handler):
"""
user management handler
"""
@ -43,53 +43,61 @@ class User(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
database = SQLite.load(configuration)
if args.action == Action.Update:
salt = User.get_salt(configuration)
user = User.user_create(args)
salt = Users.get_salt(configuration)
user = Users.user_create(args)
auth_configuration = User.configuration_get(configuration.include)
auth_configuration = Users.configuration_get(configuration.include)
User.configuration_create(auth_configuration, user, salt, args.as_service, args.secure)
Users.configuration_create(auth_configuration, user, salt, args.as_service, args.secure)
database.user_update(user.hash_password(salt))
elif args.action == Action.List:
users = database.user_list(args.username, args.role)
User.check_if_empty(args.exit_code, not users)
Users.check_if_empty(args.exit_code, not users)
for user in users:
UserPrinter(user).print(verbose=True)
elif args.action == Action.Remove:
database.user_remove(args.username)
@staticmethod
def configuration_create(configuration: Configuration, user: MUser, salt: str,
def configuration_create(configuration: Configuration, user: User, salt: str,
as_service_user: bool, secure: bool) -> None:
"""
enable configuration if it has been disabled
:param configuration: configuration instance
:param user: user descriptor
:param salt: password hash salt
:param as_service_user: add user as service user, also set password and user to configuration
:param secure: if true then set file permissions to 0o600
Args:
configuration(Configuration): configuration instance
user(User): user descriptor
salt(str): password hash salt
as_service_user(bool): add user as service user, also set password and user to configuration
secure(bool): if true then set file permissions to 0o600
"""
configuration.set_option("auth", "salt", salt)
if as_service_user:
configuration.set_option("web", "username", user.username)
configuration.set_option("web", "password", user.password)
User.configuration_write(configuration, secure)
Users.configuration_write(configuration, secure)
@staticmethod
def configuration_get(include_path: Path) -> Configuration:
"""
create configuration instance
:param include_path: path to directory with configuration includes
:return: configuration instance. In case if there are local settings they will be loaded
Args:
include_path(Path): path to directory with configuration includes
Returns:
Configuration: configuration instance. In case if there are local settings they will be loaded
"""
target = include_path / "auth.ini"
configuration = Configuration()
@ -103,8 +111,10 @@ class User(Handler):
def configuration_write(configuration: Configuration, secure: bool) -> None:
"""
write configuration file
:param configuration: configuration instance
:param secure: if true then set file permissions to 0o600
Args:
configuration(Configuration): configuration instance
secure(bool): if true then set file permissions to 0o600
"""
path, _ = configuration.check_loaded()
with path.open("w") as ahriman_configuration:
@ -116,22 +126,30 @@ class User(Handler):
def get_salt(configuration: Configuration, salt_length: int = 20) -> str:
"""
get salt from configuration or create new string
:param configuration: configuration instance
:param salt_length: salt length
:return: current salt
Args:
configuration(Configuration): configuration instance
salt_length(int, optional): salt length (Default value = 20)
Returns:
str: current salt
"""
if salt := configuration.get("auth", "salt", fallback=None):
return salt
return MUser.generate_password(salt_length)
return User.generate_password(salt_length)
@staticmethod
def user_create(args: argparse.Namespace) -> MUser:
def user_create(args: argparse.Namespace) -> User:
"""
create user descriptor from arguments
:param args: command line args
:return: built user descriptor
Args:
args(argparse.Namespace): command line args
Returns:
User: built user descriptor
"""
user = MUser(args.username, args.password, args.role)
user = User(args.username, args.password, args.role)
if user.password is None:
user.password = getpass.getpass()
return user

View File

@ -21,7 +21,7 @@ import argparse
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.spawn import Spawn
@ -39,11 +39,13 @@ class Web(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
# we are using local import for optional dependencies
from ahriman.web.web import run_server, setup_service

View File

@ -37,19 +37,36 @@ from ahriman.models.build_status import BuildStatusEnum
class Lock:
"""
wrapper for application lock file
:ivar force: remove lock file on start if any
:ivar path: path to lock file if any
:ivar reporter: build status reporter instance
:ivar paths: repository paths instance
:ivar unsafe: skip user check
Attributes:
force(bool): remove lock file on start if any
path(Path): path to lock file if any
reporter(Client): build status reporter instance
paths(RepositoryPaths): repository paths instance
unsafe(bool): skip user check
Examples:
Instance of this class except for controlling file-based lock is also required for basic applications checks.
The common flow is to create instance in ``with`` block and handle exceptions after all::
>>> from ahriman.core.configuration import Configuration
>>>
>>> configuration = Configuration()
>>> try:
>>> with Lock(args, "x86_64", configuration):
>>> perform_actions()
>>> except Exception as exception:
>>> handle_exceptions(exception)
"""
def __init__(self, args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
"""
default constructor
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.path = Path(f"{args.lock}_{architecture}") if args.lock is not None else None
self.force = args.force
@ -62,11 +79,11 @@ class Lock:
"""
default workflow is the following:
check user UID
check if there is lock file
check web status watcher status
create lock file
report to web if enabled
1. Check user UID
2. Check if there is lock file
3. Check web status watcher status
4. Create lock file
5. Report to status page if enabled
"""
self.check_user()
self.check_version()
@ -78,10 +95,14 @@ class Lock:
exc_tb: TracebackType) -> Literal[False]:
"""
remove lock file when done
:param exc_type: exception type name if any
:param exc_val: exception raised if any
:param exc_tb: exception traceback if any
:return: always False (do not suppress any exception)
Args:
exc_type(Optional[Type[Exception]]): exception type name if any
exc_val(Optional[Exception]): exception raised if any
exc_tb(TracebackType): exception traceback if any
Returns:
Literal[False]: always False (do not suppress any exception)
"""
self.clear()
status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed
@ -116,6 +137,9 @@ class Lock:
def create(self) -> None:
"""
create lock file
Raises:
DuplicateRun: if lock exists and no force flag supplied
"""
if self.path is None:
return

View File

@ -17,8 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from pyalpm import Handle # type: ignore
from typing import Set
from pyalpm import Handle, Package, SIG_PACKAGE # type: ignore
from typing import Generator, Set
from ahriman.core.configuration import Configuration
@ -26,24 +26,30 @@ from ahriman.core.configuration import Configuration
class Pacman:
"""
alpm wrapper
:ivar handle: pyalpm root `Handle`
Attributes:
handle(Handle): pyalpm root ``Handle``
"""
def __init__(self, configuration: Configuration) -> None:
"""
default constructor
:param configuration: configuration instance
Args:
configuration(Configuration): configuration instance
"""
root = configuration.get("alpm", "root")
pacman_root = configuration.getpath("alpm", "database")
self.handle = Handle(root, str(pacman_root))
for repository in configuration.getlist("alpm", "repositories"):
self.handle.register_syncdb(repository, 0) # 0 is pgp_level
self.handle.register_syncdb(repository, SIG_PACKAGE)
def all_packages(self) -> Set[str]:
"""
get list of packages known for alpm
:return: list of package names
Returns:
Set[str]: list of package names
"""
result: Set[str] = set()
for database in self.handle.get_syncdbs():
@ -52,3 +58,19 @@ class Pacman:
result.update(package.provides) # provides list for meta-packages
return result
def get(self, package_name: str) -> Generator[Package, None, None]:
"""
retrieve list of the packages from the repository by name
Args:
package_name(str): package name to search
Yields:
Package: list of packages which were returned by the query
"""
for database in self.handle.get_syncdbs():
package = database.get_pkg(package_name)
if package is None:
continue
yield package

View File

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

View File

@ -19,9 +19,10 @@
#
import requests
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Type
from ahriman.core.alpm.remote.remote import Remote
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import Remote
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text
from ahriman.models.aur_package import AURPackage
@ -30,31 +31,30 @@ from ahriman.models.aur_package import AURPackage
class AUR(Remote):
"""
AUR RPC wrapper
:cvar DEFAULT_RPC_URL: default AUR RPC url
:cvar DEFAULT_RPC_VERSION: default AUR RPC version
:ivar rpc_url: AUR RPC url
:ivar rpc_version: AUR RPC version
Attributes:
DEFAULT_AUR_URL(str): (class attribute) default AUR url
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
"""
DEFAULT_RPC_URL = "https://aur.archlinux.org/rpc"
DEFAULT_AUR_URL = "https://aur.archlinux.org"
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc"
DEFAULT_RPC_VERSION = "5"
def __init__(self, rpc_url: Optional[str] = None, rpc_version: Optional[str] = None) -> None:
"""
default constructor
:param rpc_url: AUR RPC url
:param rpc_version: AUR RPC version
"""
Remote.__init__(self)
self.rpc_url = rpc_url or self.DEFAULT_RPC_URL
self.rpc_version = rpc_version or self.DEFAULT_RPC_VERSION
@staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
"""
parse RPC response to package list
:param response: RPC response json
:return: list of parsed packages
Args:
response(Dict[str, Any]): RPC response json
Returns:
List[AURPackage]: list of parsed packages
Raises:
InvalidPackageInfo: for error API response
"""
response_type = response["type"]
if response_type == "error":
@ -62,17 +62,48 @@ class AUR(Remote):
raise InvalidPackageInfo(error_details)
return [AURPackage.from_json(package) for package in response["results"]]
@classmethod
def remote_git_url(cls: Type[Remote], package_base: str, repository: str) -> str:
"""
generate remote git url from the package base
Args
package_base(str): package base
repository(str): repository name
Returns:
str: git url for the specific base
"""
return f"{AUR.DEFAULT_AUR_URL}/{package_base}.git"
@classmethod
def remote_web_url(cls: Type[Remote], package_base: str) -> str:
"""
generate remote web url from the package base
Args
package_base(str): package base
Returns:
str: web url for the specific base
"""
return f"{AUR.DEFAULT_AUR_URL}/packages/{package_base}"
def make_request(self, request_type: str, *args: str, **kwargs: str) -> List[AURPackage]:
"""
perform request to AUR RPC
:param request_type: AUR request type, e.g. search, info
:param args: list of arguments to be passed as args query parameter
:param kwargs: list of additional named parameters like by
:return: response parsed to package list
Args:
request_type(str): AUR request type, e.g. search, info
*args(str): list of arguments to be passed as args query parameter
**kwargs(str): list of additional named parameters like by
Returns:
List[AURPackage]: response parsed to package list
"""
query: Dict[str, Any] = {
"type": request_type,
"v": self.rpc_version
"v": self.DEFAULT_RPC_VERSION
}
arg_query = "arg[]" if len(args) > 1 else "arg"
@ -82,7 +113,7 @@ class AUR(Remote):
query[key] = value
try:
response = requests.get(self.rpc_url, params=query)
response = requests.get(self.DEFAULT_RPC_URL, params=query)
response.raise_for_status()
return self.parse_response(response.json())
except requests.HTTPError as e:
@ -95,19 +126,29 @@ class AUR(Remote):
self.logger.exception("could not perform request by using type %s", request_type)
raise
def package_info(self, package_name: str) -> AURPackage:
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
"""
get package info by its name
:param package_name: package name to search
:return: package which match the package name
Args:
package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
"""
packages = self.make_request("info", package_name)
return next(package for package in packages if package.name == package_name)
def package_search(self, *keywords: str) -> List[AURPackage]:
def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]:
"""
search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria
Args:
*keywords(str): keywords to search
pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages which match the criteria
"""
return self.make_request("search", *keywords, by="name-desc")

View File

@ -19,9 +19,10 @@
#
import requests
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Type
from ahriman.core.alpm.remote.remote import Remote
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import Remote
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text
from ahriman.models.aur_package import AURPackage
@ -30,40 +31,77 @@ from ahriman.models.aur_package import AURPackage
class Official(Remote):
"""
official repository RPC wrapper
:cvar DEFAULT_RPC_URL: default AUR RPC url
:ivar rpc_url: AUR RPC url
Attributes:
DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url
DEFAULT_SEARCH_REPOSITORIES(List[str]): (class attribute) default list of repositories to search
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
"""
DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib", "Community"]
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
def __init__(self, rpc_url: Optional[str] = None) -> None:
"""
default constructor
:param rpc_url: AUR RPC url
"""
Remote.__init__(self)
self.rpc_url = rpc_url or self.DEFAULT_RPC_URL
@staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
"""
parse RPC response to package list
:param response: RPC response json
:return: list of parsed packages
Args:
response(Dict[str, Any]): RPC response json
Returns:
List[AURPackage]: list of parsed packages
Raises:
InvalidPackageInfo: for error API response
"""
if not response["valid"]:
raise InvalidPackageInfo("API validation error")
return [AURPackage.from_repo(package) for package in response["results"]]
@classmethod
def remote_git_url(cls: Type[Remote], package_base: str, repository: str) -> str:
"""
generate remote git url from the package base
Args
package_base(str): package base
repository(str): repository name
Returns:
str: git url for the specific base
"""
if repository.lower() in ("core", "extra", "testing", "kde-unstable"):
return "https://github.com/archlinux/svntogit-packages.git" # hardcoded, ok
return "https://github.com/archlinux/svntogit-community.git"
@classmethod
def remote_web_url(cls: Type[Remote], package_base: str) -> str:
"""
generate remote web url from the package base
Args
package_base(str): package base
Returns:
str: web url for the specific base
"""
return f"{Official.DEFAULT_ARCHLINUX_URL}/packages/{package_base}"
def make_request(self, *args: str, by: str) -> List[AURPackage]:
"""
perform request to official repositories RPC
:param args: list of arguments to be passed as args query parameter
:param by: search by the field
:return: response parsed to package list
Args:
*args(str): list of arguments to be passed as args query parameter
by(str): search by the field
Returns:
List[AURPackage]: response parsed to package list
"""
try:
response = requests.get(self.rpc_url, params={by: args})
response = requests.get(self.DEFAULT_RPC_URL, params={by: args, "repo": self.DEFAULT_SEARCH_REPOSITORIES})
response.raise_for_status()
return self.parse_response(response.json())
except requests.HTTPError as e:
@ -73,19 +111,29 @@ class Official(Remote):
self.logger.exception("could not perform request")
raise
def package_info(self, package_name: str) -> AURPackage:
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
"""
get package info by its name
:param package_name: package name to search
:return: package which match the package name
Args:
package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
"""
packages = self.make_request(package_name, by="name")
return next(package for package in packages if package.name == package_name)
def package_search(self, *keywords: str) -> List[AURPackage]:
def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]:
"""
search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria
Args:
*keywords(str): keywords to search
pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages which match the criteria
"""
return self.make_request(*keywords, by="q")

View File

@ -0,0 +1,51 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import Official
from ahriman.models.aur_package import AURPackage
class OfficialSyncdb(Official):
"""
official repository wrapper based on synchronized databases.
Despite the fact that official repository provides an API for the interaction according to the comment in issue
https://github.com/arcan1s/ahriman/pull/59#issuecomment-1106412297 we might face rate limits while requesting
updates.
This approach also has limitations, because we don't require superuser rights (neither going to download database
separately), the database file might be outdated and must be handled manually (or kind of). This behaviour might be
changed in the future.
Still we leave search function based on the official repositories RPC.
"""
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
"""
get package info by its name
Args:
package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
"""
return next(AURPackage.from_pacman(package) for package in pacman.get(package_name))

View File

@ -23,13 +23,28 @@ import logging
from typing import Dict, List, Type
from ahriman.core.alpm.pacman import Pacman
from ahriman.models.aur_package import AURPackage
class Remote:
"""
base class for remote package search
:ivar logger: class logger
Attributes:
logger(logging.Logger): class logger
Examples:
These classes are designed to be used without instancing. In order to achieve it several class methods are
provided: ``info``, ``multisearch`` and ``search``. Thus, the basic flow is the following::
>>> from ahriman.core.alpm.remote import AUR, Official
>>>
>>> package = AUR.info("ahriman", pacman=pacman)
>>> search_result = Official.multisearch("pacman", "manager", pacman=pacman)
Differnece between ``search`` and ``multisearch`` is that ``search`` passes all arguments to underlying wrapper
directly, whereas ``multisearch`` splits search one by one and finds intersection between search results.
"""
def __init__(self) -> None:
@ -39,26 +54,36 @@ class Remote:
self.logger = logging.getLogger("build_details")
@classmethod
def info(cls: Type[Remote], package_name: str) -> AURPackage:
def info(cls: Type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage:
"""
get package info by its name
:param package_name: package name to search
:return: package which match the package name
Args:
package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
"""
return cls().package_info(package_name)
return cls().package_info(package_name, pacman=pacman)
@classmethod
def multisearch(cls: Type[Remote], *keywords: str) -> List[AURPackage]:
def multisearch(cls: Type[Remote], *keywords: str, pacman: Pacman) -> List[AURPackage]:
"""
search in remote repository by using API with multiple words. This method is required in order to handle
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
:param keywords: search terms, e.g. "ahriman", "is", "cool"
:return: list of packages each of them matches all search terms
Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages each of them matches all search terms
"""
instance = cls()
packages: Dict[str, AURPackage] = {}
for term in filter(lambda word: len(word) > 3, keywords):
portion = instance.search(term)
portion = instance.search(term, pacman=pacman)
packages = {
package.name: package # not mistake to group them by name
for package in portion
@ -67,26 +92,80 @@ class Remote:
return list(packages.values())
@classmethod
def search(cls: Type[Remote], *keywords: str) -> List[AURPackage]:
def remote_git_url(cls: Type[Remote], package_base: str, repository: str) -> str:
"""
generate remote git url from the package base
Args
package_base(str): package base
repository(str): repository name
Returns:
str: git url for the specific base
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
@classmethod
def remote_web_url(cls: Type[Remote], package_base: str) -> str:
"""
generate remote web url from the package base
Args
package_base(str): package base
Returns:
str: web url for the specific base
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
@classmethod
def search(cls: Type[Remote], *keywords: str, pacman: Pacman) -> List[AURPackage]:
"""
search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria
"""
return cls().package_search(*keywords)
def package_info(self, package_name: str) -> AURPackage:
Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages which match the criteria
"""
return cls().package_search(*keywords, pacman=pacman)
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
"""
get package info by its name
:param package_name: package name to search
:return: package which match the package name
Args:
package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_search(self, *keywords: str) -> List[AURPackage]:
def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]:
"""
search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria
Args:
*keywords(str): keywords to search
pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages which match the criteria
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError

View File

@ -30,11 +30,13 @@ from ahriman.models.repository_paths import RepositoryPaths
class Repo:
"""
repo-add and repo-remove wrapper
:ivar logger: class logger
:ivar name: repository name
:ivar paths: repository paths instance
:ivar sign_args: additional args which have to be used to sign repository archive
:ivar uid: uid of the repository owner user
Attributes:
logger(logging.Logger): class logger
name(str): repository name
paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive
uid(int): uid of the repository owner user
"""
_check_output = check_output
@ -42,9 +44,11 @@ class Repo:
def __init__(self, name: str, paths: RepositoryPaths, sign_args: List[str]) -> None:
"""
default constructor
:param name: repository name
:param paths: repository paths instance
:param sign_args: additional args which have to be used to sign repository archive
Args:
name(str): repository name
paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive
"""
self.logger = logging.getLogger("build_details")
self.name = name
@ -55,14 +59,19 @@ class Repo:
@property
def repo_path(self) -> Path:
"""
:return: path to repository database
get full path to the repository database
Returns:
Path: path to repository database
"""
return self.paths.repository / f"{self.name}.db.tar.gz"
def add(self, path: Path) -> None:
"""
add new package to repository
:param path: path to archive to add
Args:
path(Path): path to archive to add
"""
Repo._check_output(
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path),
@ -85,8 +94,10 @@ class Repo:
def remove(self, package: str, filename: Path) -> None:
"""
remove package from repository
:param package: package name to remove
:param filename: package filename to remove
Args:
package(str): package name to remove
filename(Path): package filename to remove
"""
# remove package and signature (if any) from filesystem
for full_path in self.paths.repository.glob(f"{filename}*"):

View File

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

View File

@ -24,7 +24,7 @@ import logging
from typing import Optional, Type
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user_access import UserAccess
@ -32,16 +32,21 @@ from ahriman.models.user_access import UserAccess
class Auth:
"""
helper to deal with user authorization
:ivar enabled: indicates if authorization is enabled
:ivar max_age: session age in seconds. It will be used for both client side and server side checks
:ivar safe_build_status: allow read only access to the index page
Attributes:
enabled(bool): indicates if authorization is enabled
logger(logging.Logger): class logger
max_age(int): session age in seconds. It will be used for both client side and server side checks
safe_build_status(bool): allow read only access to the index page
"""
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
"""
default constructor
:param configuration: configuration instance
:param provider: authorization type definition
Args:
configuration(Configuration): configuration instance
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.Disabled)
"""
self.logger = logging.getLogger("http")
@ -57,7 +62,9 @@ class Auth:
In case of internal authentication it must provide an interface (modal form) to login with button sends POST
request. But for an external providers behaviour can be different: e.g. OAuth provider requires sending GET
request to external resource
:return: login control as html code to insert
Returns:
str: login control as html code to insert
"""
return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#loginForm" style="text-decoration: none">login</button>"""
@ -65,25 +72,33 @@ class Auth:
def load(cls: Type[Auth], configuration: Configuration, database: SQLite) -> Auth:
"""
load authorization module from settings
:param configuration: configuration instance
:param database: database instance
:return: authorization module according to current settings
Args:
configuration(Configuration): configuration instance
database(SQLite): database instance
Returns:
Auth: authorization module according to current settings
"""
provider = AuthSettings.from_option(configuration.get("auth", "target", fallback="disabled"))
if provider == AuthSettings.Configuration:
from ahriman.core.auth.mapping import Mapping
from ahriman.core.auth import Mapping
return Mapping(configuration, database)
if provider == AuthSettings.OAuth:
from ahriman.core.auth.oauth import OAuth
from ahriman.core.auth import OAuth
return OAuth(configuration, database)
return cls(configuration)
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool: # pylint: disable=no-self-use
"""
validate user password
:param username: username
:param password: entered password
:return: True in case if password matches, False otherwise
Args:
username(Optional[str]): username
password(Optional[str]): entered password
Returns:
bool: True in case if password matches, False otherwise
"""
del username, password
return True
@ -91,8 +106,12 @@ class Auth:
async def known_username(self, username: Optional[str]) -> bool: # pylint: disable=no-self-use
"""
check if user is known
:param username: username
:return: True in case if user is known and can be authorized and False otherwise
Args:
username(Optional[str]): username
Returns:
bool: True in case if user is known and can be authorized and False otherwise
"""
del username
return True
@ -100,10 +119,14 @@ class Auth:
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool: # pylint: disable=no-self-use
"""
validate if user has access to requested resource
:param username: username
:param required: required access level
:param context: URI request path
:return: True in case if user is allowed to do this request and False otherwise
Args:
username(str): username
required(UserAccess): required access level
context(Optional[str]): URI request path
Returns:
bool: True in case if user is allowed to do this request and False otherwise
"""
del username, required, context
return True

View File

@ -26,11 +26,18 @@ except ImportError:
_has_aiohttp_security = False
__all__ = ["authorized_userid", "check_authorized", "forget", "remember"]
async def authorized_userid(*args: Any) -> Any:
"""
handle aiohttp security methods
:param args: argument list as provided by authorized_userid function
:return: None in case if no aiohttp_security module found and function call otherwise
Args:
*args(Any): argument list as provided by authorized_userid function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
return await aiohttp_security.authorized_userid(*args) # pylint: disable=no-value-for-parameter
@ -40,8 +47,12 @@ async def authorized_userid(*args: Any) -> Any:
async def check_authorized(*args: Any) -> Any:
"""
handle aiohttp security methods
:param args: argument list as provided by check_authorized function
:return: None in case if no aiohttp_security module found and function call otherwise
Args:
*args(Any): argument list as provided by check_authorized function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
return await aiohttp_security.check_authorized(*args) # pylint: disable=no-value-for-parameter
@ -51,8 +62,12 @@ async def check_authorized(*args: Any) -> Any:
async def forget(*args: Any) -> Any:
"""
handle aiohttp security methods
:param args: argument list as provided by forget function
:return: None in case if no aiohttp_security module found and function call otherwise
Args:
*args(Any): argument list as provided by forget function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
return await aiohttp_security.forget(*args) # pylint: disable=no-value-for-parameter
@ -62,8 +77,12 @@ async def forget(*args: Any) -> Any:
async def remember(*args: Any) -> Any:
"""
handle disabled auth
:param args: argument list as provided by remember function
:return: None in case if no aiohttp_security module found and function call otherwise
Args:
*args(Any): argument list as provided by remember function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
return await aiohttp_security.remember(*args) # pylint: disable=no-value-for-parameter

View File

@ -19,10 +19,9 @@
#
from typing import Optional
from ahriman.core.auth.auth import Auth
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user import User
from ahriman.models.user_access import UserAccess
@ -31,17 +30,21 @@ from ahriman.models.user_access import UserAccess
class Mapping(Auth):
"""
user authorization based on mapping from configuration file
:ivar salt: random generated string to salt passwords
:ivar database: database instance
Attributes:
salt(str): random generated string to salt passwords
database(SQLite): database instance
"""
def __init__(self, configuration: Configuration, database: SQLite,
provider: AuthSettings = AuthSettings.Configuration) -> None:
"""
default constructor
:param configuration: configuration instance
:param database: database instance
:param provider: authorization type definition
Args:
configuration(Configuration): configuration instance
database(SQLite): database instance
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.Configuration)
"""
Auth.__init__(self, configuration, provider)
self.database = database
@ -50,9 +53,13 @@ class Mapping(Auth):
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool:
"""
validate user password
:param username: username
:param password: entered password
:return: True in case if password matches, False otherwise
Args:
username(Optional[str]): username
password(Optional[str]): entered password
Returns:
bool: True in case if password matches, False otherwise
"""
if username is None or password is None:
return False # invalid data supplied
@ -62,26 +69,38 @@ class Mapping(Auth):
def get_user(self, username: str) -> Optional[User]:
"""
retrieve user from in-memory mapping
:param username: username
:return: user descriptor if username is known and None otherwise
Args:
username(str): username
Returns:
Optional[User]: user descriptor if username is known and None otherwise
"""
return self.database.user_get(username)
async def known_username(self, username: Optional[str]) -> bool:
"""
check if user is known
:param username: username
:return: True in case if user is known and can be authorized and False otherwise
Args:
username(Optional[str]): username
Returns:
bool: True in case if user is known and can be authorized and False otherwise
"""
return username is not None and self.get_user(username) is not None
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool:
"""
validate if user has access to requested resource
:param username: username
:param required: required access level
:param context: URI request path
:return: True in case if user is allowed to do this request and False otherwise
Args:
username(str): username
required(UserAccess): required access level
context(Optional[str]): URI request path
Returns:
bool: True in case if user is allowed to do this request and False otherwise
"""
user = self.get_user(username)
return user is not None and user.verify_access(required)

View File

@ -21,9 +21,9 @@ import aioauth_client
from typing import Optional, Type
from ahriman.core.auth.mapping import Mapping
from ahriman.core.auth import Mapping
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.exceptions import InvalidOption
from ahriman.models.auth_settings import AuthSettings
@ -32,20 +32,24 @@ class OAuth(Mapping):
"""
OAuth user authorization.
It is required to create application first and put application credentials.
:ivar client_id: application client id
:ivar client_secret: application client secret key
:ivar provider: provider class, should be one of aiohttp-client provided classes
:ivar redirect_uri: redirect URI registered in provider
:ivar scopes: list of scopes required by the application
Attributes:
client_id(str): application client id
client_secret(str): application client secret key
provider(aioauth_client.OAuth2Client): provider class, should be one of aiohttp-client provided classes
redirect_uri(str): redirect URI registered in provider
scopes(str): list of scopes required by the application
"""
def __init__(self, configuration: Configuration, database: SQLite,
provider: AuthSettings = AuthSettings.OAuth) -> None:
"""
default constructor
:param configuration: configuration instance
:param database: database instance
:param provider: authorization type definition
Args:
configuration(Configuration): configuration instance
database(SQLite): database instance
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.OAuth)
"""
Mapping.__init__(self, configuration, database, provider)
self.client_id = configuration.get("auth", "client_id")
@ -60,7 +64,10 @@ class OAuth(Mapping):
@property
def auth_control(self) -> str:
"""
:return: login control as html code to insert
get authorization html control
Returns:
str: login control as html code to insert
"""
return """<a class="nav-link" href="/user-api/v1/login" title="login via OAuth2">login</a>"""
@ -68,8 +75,15 @@ class OAuth(Mapping):
def get_provider(name: str) -> Type[aioauth_client.OAuth2Client]:
"""
load OAuth2 provider by name
:param name: name of the provider. Must be valid class defined in aioauth-client library
:return: loaded provider type
Args:
name(str): name of the provider. Must be valid class defined in aioauth-client library
Returns:
Type[aioauth_client.OAuth2Client]: loaded provider type
Raises:
InvalidOption: in case if invalid OAuth provider name supplied
"""
provider: Type[aioauth_client.OAuth2Client] = getattr(aioauth_client, name)
try:
@ -83,14 +97,18 @@ class OAuth(Mapping):
def get_client(self) -> aioauth_client.OAuth2Client:
"""
load client from parameters
:return: generated client according to current settings
Returns:
aioauth_client.OAuth2Client: generated client according to current settings
"""
return self.provider(client_id=self.client_id, client_secret=self.client_secret)
def get_oauth_url(self) -> str:
"""
get authorization URI for the specified settings
:return: authorization URI as a string
Returns:
str: authorization URI as a string
"""
client = self.get_client()
uri: str = client.get_authorize_url(scope=self.scopes, redirect_uri=self.redirect_uri)
@ -99,8 +117,12 @@ class OAuth(Mapping):
async def get_oauth_username(self, code: str) -> Optional[str]:
"""
extract OAuth username from remote
:param code: authorization code provided by external service
:return: username as is in OAuth provider
Args:
code(str): authorization code provided by external service
Returns:
Optional[str]: username as is in OAuth provider
"""
try:
client = self.get_client()

View File

@ -18,30 +18,40 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import shutil
from pathlib import Path
from typing import List, Optional
from ahriman.core.util import check_output
from ahriman.core.util import check_output, walk
from ahriman.models.package import Package
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths
class Sources:
"""
helper to download package sources (PKGBUILD etc)
:cvar logger: class logger
Attributes:
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages
logger(logging.Logger): (class attribute) class logger
"""
DEFAULT_BRANCH = "master" # default fallback branch
logger = logging.getLogger("build_details")
_branch = "master" # in case if BLM would like to change it
_check_output = check_output
@staticmethod
def add(sources_dir: Path, *pattern: str) -> None:
def _add(sources_dir: Path, *pattern: str) -> None:
"""
track found files via git
:param sources_dir: local path to git repository
:param pattern: glob patterns
Args:
sources_dir(Path): local path to git repository
*pattern(str): glob patterns
"""
# glob directory to find files which match the specified patterns
found_files: List[Path] = []
@ -56,20 +66,41 @@ class Sources:
exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod
def diff(sources_dir: Path) -> str:
def _diff(sources_dir: Path) -> str:
"""
generate diff from the current version and write it to the output file
:param sources_dir: local path to git repository
:return: patch as plain string
Args:
sources_dir(Path): local path to git repository
Returns:
str: patch as plain string
"""
return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod
def fetch(sources_dir: Path, remote: Optional[str]) -> None:
def _move(pkgbuild_dir: Path, sources_dir: Path) -> None:
"""
either clone repository or update it to origin/`branch`
:param sources_dir: local path to fetch
:param remote: remote target (from where to fetch)
move content from pkgbuild_dir to sources_dir
Args:
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
sources_dir(Path): path to target directory
"""
if pkgbuild_dir == sources_dir:
return # directories are the same, no need to move
for src in walk(pkgbuild_dir):
dst = sources_dir / src.relative_to(pkgbuild_dir)
shutil.move(src, dst)
@staticmethod
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
"""
either clone repository or update it to origin/``remote.branch``
Args:
sources_dir(Path): local path to fetch
remote(Optional[RemoteSource]): remote target (from where to fetch)
"""
# local directory exists and there is .git directory
is_initialized_git = (sources_dir / ".git").is_dir()
@ -78,28 +109,41 @@ class Sources:
Sources.logger.info("skip update at %s because there are no branches configured", sources_dir)
return
branch = remote.branch if remote is not None else Sources.DEFAULT_BRANCH
if is_initialized_git:
Sources.logger.info("update HEAD to remote at %s", sources_dir)
Sources._check_output("git", "fetch", "origin", Sources._branch,
Sources.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
Sources._check_output("git", "fetch", "origin", branch,
exception=None, cwd=sources_dir, logger=Sources.logger)
elif remote is not None:
Sources.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
remote.git_url, str(sources_dir),
exception=None, cwd=sources_dir, logger=Sources.logger)
elif remote is None:
Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
else:
Sources.logger.info("clone remote %s to %s", remote, sources_dir)
Sources._check_output("git", "clone", remote, str(sources_dir),
exception=None, cwd=sources_dir, logger=Sources.logger)
# it will cause an exception later
Sources.logger.error("%s is not initialized, but no remote provided", sources_dir)
# and now force reset to our branch
Sources._check_output("git", "checkout", "--force", Sources._branch,
Sources._check_output("git", "checkout", "--force", branch,
exception=None, cwd=sources_dir, logger=Sources.logger)
Sources._check_output("git", "reset", "--hard", f"origin/{Sources._branch}",
Sources._check_output("git", "reset", "--hard", f"origin/{branch}",
exception=None, cwd=sources_dir, logger=Sources.logger)
# move content if required
# we are using full path to source directory in order to make append possible
pkgbuild_dir = remote.pkgbuild_dir if remote is not None else sources_dir.resolve()
Sources._move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
@staticmethod
def has_remotes(sources_dir: Path) -> bool:
"""
check if there are remotes for the repository
:param sources_dir: local path to git repository
:return: True in case if there is any remote and false otherwise
Args:
sources_dir(Path): local path to git repository
Returns:
bool: True in case if there is any remote and false otherwise
"""
remotes = Sources._check_output("git", "remote", exception=None, cwd=sources_dir, logger=Sources.logger)
return bool(remotes)
@ -108,20 +152,29 @@ class Sources:
def init(sources_dir: Path) -> None:
"""
create empty git repository at the specified path
:param sources_dir: local path to sources
Args:
sources_dir(Path): local path to sources
"""
Sources._check_output("git", "init", "--initial-branch", Sources._branch,
Sources._check_output("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH,
exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod
def load(sources_dir: Path, remote: str, patch: Optional[str]) -> None:
def load(sources_dir: Path, package: Package, patch: Optional[str], paths: RepositoryPaths) -> None:
"""
fetch sources from remote and apply patches
:param sources_dir: local path to fetch
:param remote: remote target (from where to fetch)
:param patch: optional patch to be applied
Args:
sources_dir(Path): local path to fetch
package(Package): package definitions
patch(Optional[str]): optional patch to be applied
paths(RepositoryPaths): repository paths instance
"""
Sources.fetch(sources_dir, remote)
if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir:
# no need to clone whole repository, just copy from cache first
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
Sources.fetch(sources_dir, package.remote)
if patch is None:
Sources.logger.info("no patches found")
return
@ -131,8 +184,10 @@ class Sources:
def patch_apply(sources_dir: Path, patch: str) -> None:
"""
apply patches if any
:param sources_dir: local path to directory with git sources
:param patch: patch to be applied
Args:
sources_dir(Path): local path to directory with git sources
patch(str): patch to be applied
"""
# create patch
Sources.logger.info("apply patch from database")
@ -143,10 +198,14 @@ class Sources:
def patch_create(sources_dir: Path, *pattern: str) -> str:
"""
create patch set for the specified local path
:param sources_dir: local path to git repository
:param pattern: glob patterns
:return: patch as plain text
Args:
sources_dir(Path): local path to git repository
*pattern(str): glob patterns
Returns:
str: patch as plain text
"""
Sources.add(sources_dir, *pattern)
diff = Sources.diff(sources_dir)
Sources._add(sources_dir, *pattern)
diff = Sources._diff(sources_dir)
return f"{diff}\n" # otherwise, patch will be broken

View File

@ -18,14 +18,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import shutil
from pathlib import Path
from typing import List
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.exceptions import BuildFailed
from ahriman.core.util import check_output
from ahriman.models.package import Package
@ -35,11 +34,13 @@ from ahriman.models.repository_paths import RepositoryPaths
class Task:
"""
base package build task
:ivar build_logger: logger for build process
:ivar logger: class logger
:ivar package: package definitions
:ivar paths: repository paths instance
:ivar uid: uid of the repository owner user
Attributes:
build_logger(logging.Logger): logger for build process
logger(logging.Logger): class logger
package(Package): package definitions
paths(RepositoryPaths): repository paths instance
uid(int): uid of the repository owner user
"""
_check_output = check_output
@ -47,9 +48,11 @@ class Task:
def __init__(self, package: Package, configuration: Configuration, paths: RepositoryPaths) -> None:
"""
default constructor
:param package: package definitions
:param configuration: configuration instance
:param paths: repository paths instance
Args:
package(Package): package definitions
configuration(Configuration): configuration instance
paths(RepositoryPaths): repository paths instance
"""
self.logger = logging.getLogger("root")
self.build_logger = logging.getLogger("build_details")
@ -62,11 +65,15 @@ class Task:
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
def build(self, sources_path: Path) -> List[Path]:
def build(self, sources_dir: Path) -> List[Path]:
"""
run package build
:param sources_path: path to where sources are
:return: paths of produced packages
Args:
sources_dir(Path): path to where sources are
Returns:
List[Path]: paths of produced packages
"""
command = [self.build_command, "-r", str(self.paths.chroot)]
command.extend(self.archbuild_flags)
@ -77,24 +84,23 @@ class Task:
Task._check_output(
*command,
exception=BuildFailed(self.package.base),
cwd=sources_path,
cwd=sources_dir,
logger=self.build_logger,
user=self.uid)
# well it is not actually correct, but we can deal with it
packages = Task._check_output("makepkg", "--packagelist",
exception=BuildFailed(self.package.base),
cwd=sources_path,
cwd=sources_dir,
logger=self.build_logger).splitlines()
return [Path(package) for package in packages]
def init(self, path: Path, database: SQLite) -> None:
def init(self, sources_dir: Path, database: SQLite) -> None:
"""
fetch package from git
:param path: local path to fetch
:param database: database instance
Args:
sources_dir(Path): local path to fetch
database(SQLite): database instance
"""
if self.paths.cache_for(self.package.base).is_dir():
# no need to clone whole repository, just copy from cache first
shutil.copytree(self.paths.cache_for(self.package.base), path, dirs_exist_ok=True)
Sources.load(path, self.package.git_url, database.patches_get(self.package.base))
Sources.load(sources_dir, self.package, database.patches_get(self.package.base), self.paths)

View File

@ -34,12 +34,34 @@ from ahriman.models.repository_paths import RepositoryPaths
class Configuration(configparser.RawConfigParser):
"""
extension for built-in configuration parser
:ivar architecture: repository architecture
:ivar path: path to root configuration file
:cvar ARCHITECTURE_SPECIFIC_SECTIONS: known sections which can be architecture specific (required by dump)
:cvar DEFAULT_LOG_FORMAT: default log format (in case of fallback)
:cvar DEFAULT_LOG_LEVEL: default log level (in case of fallback)
:cvar SYSTEM_CONFIGURATION_PATH: default system configuration path distributed by package
Attributes:
ARCHITECTURE_SPECIFIC_SECTIONS(List[str]): (class attribute) known sections which can be architecture specific.
Required by dump and merging functions
DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback)
DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
architecture(Optional[str]): repository architecture
path(Optional[Path]): path to root configuration file
Examples:
Configuration class provides additional method in order to handle application configuration. Since this class is
derived from built-in ``configparser.RawConfigParser`` class, the same flow is applicable here. Nevertheless,
it is recommended to use ``from_path`` class method which also calls initialization methods::
>>> from pathlib import Path
>>>
>>> configuration = Configuration.from_path(Path("/etc/ahriman.ini"), "x86_64", quiet=False)
>>> repository_name = configuration.get("repository", "name")
>>> makepkg_flags = configuration.getlist("build", "makepkg_flags")
The configuration instance loaded in this way will contain only sections which are defined for the specified
architecture according to the merge rules. Moreover, the architecture names will be removed from section names.
In order to get current settings, the ``check_loaded`` method can be used. This method will raise an
``InitializeException`` in case if configuration was not yet loaded::
>>> path, architecture = configuration.check_loaded()
"""
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
@ -62,21 +84,30 @@ class Configuration(configparser.RawConfigParser):
@property
def include(self) -> Path:
"""
:return: path to directory with configuration includes
get full path to include directory
Returns:
Path: path to directory with configuration includes
"""
return self.getpath("settings", "include")
@property
def logging_path(self) -> Path:
"""
:return: path to logging configuration
get full path to logging configuration
Returns:
Path: path to logging configuration
"""
return self.getpath("settings", "logging")
@property
def repository_paths(self) -> RepositoryPaths:
"""
:return: repository paths instance
construct RepositoryPaths instance based on the configuration
Returns:
RepositoryPaths: repository paths instance
"""
_, architecture = self.check_loaded()
return RepositoryPaths(self.getpath("repository", "root"), architecture)
@ -85,23 +116,34 @@ class Configuration(configparser.RawConfigParser):
def from_path(cls: Type[Configuration], path: Path, architecture: str, quiet: bool) -> Configuration:
"""
constructor with full object initialization
:param path: path to root configuration file
:param architecture: repository architecture
:param quiet: force disable any log messages
:return: configuration instance
Args:
path(Path): path to root configuration file
architecture(str): repository architecture
quiet(bool): force disable any log messages
Returns:
Configuration: configuration instance
"""
config = cls()
config.load(path)
config.merge_sections(architecture)
config.load_logging(quiet)
return config
configuration = cls()
configuration.load(path)
configuration.merge_sections(architecture)
configuration.load_logging(quiet)
return configuration
@staticmethod
def __convert_list(value: str) -> List[str]:
"""
convert string value to list of strings
:param value: string configuration value
:return: list of string from the parsed string
Args:
value(str): string configuration value
Returns:
List[str]: list of string from the parsed string
Raises:
ValueError: in case if option value contains unclosed quotes
"""
def generator() -> Generator[str, None, None]:
quote_mark = None
@ -111,7 +153,7 @@ class Configuration(configparser.RawConfigParser):
quote_mark = char
elif char == quote_mark: # quoted part ended, reset quotation
quote_mark = None
elif char == " " and quote_mark is None: # found space outside of the quotation, yield the word
elif char == " " and quote_mark is None: # found space outside the quotation, yield the word
yield word
word = ""
else: # append character to the buffer
@ -126,17 +168,25 @@ class Configuration(configparser.RawConfigParser):
def section_name(section: str, suffix: str) -> str:
"""
generate section name for sections which depends on context
:param section: section name
:param suffix: session suffix, e.g. repository architecture
:return: correct section name for repository specific section
Args:
section(str): section name
suffix(str): session suffix, e.g. repository architecture
Returns:
str: correct section name for repository specific section
"""
return f"{section}:{suffix}"
def __convert_path(self, value: str) -> Path:
"""
convert string value to path object
:param value: string configuration value
:return: path object which represents the configuration value
Args:
value(str): string configuration value
Returns:
Path: path object which represents the configuration value
"""
path = Path(value)
if self.path is None or path.is_absolute():
@ -146,7 +196,12 @@ class Configuration(configparser.RawConfigParser):
def check_loaded(self) -> Tuple[Path, str]:
"""
check if service was actually loaded
:return: configuration root path and architecture if loaded
Returns:
Tuple[Path, str]: configuration root path and architecture if loaded
Raises:
InitializeException: in case if architecture and/or path are not set
"""
if self.path is None or self.architecture is None:
raise InitializeException("Configuration path and/or architecture are not set")
@ -155,7 +210,9 @@ class Configuration(configparser.RawConfigParser):
def dump(self) -> Dict[str, Dict[str, str]]:
"""
dump configuration to dictionary
:return: configuration dump for specific architecture
Returns:
Dict[str, Dict[str, str]]: configuration dump for specific architecture
"""
return {
section: dict(self[section])
@ -172,9 +229,16 @@ class Configuration(configparser.RawConfigParser):
"""
get type variable with fallback to old logic
Despite the fact that it has same semantics as other get* methods, but it has different argument list
:param section: section name
:param architecture: repository architecture
:return: section name and found type name
Args:
section(str): section name
architecture(str): repository architecture
Returns:
Tuple[str, str]: section name and found type name
Raises:
configparser.NoSectionError: in case if no section found
"""
group_type = self.get(section, "type", fallback=None) # new-style logic
if group_type is not None:
@ -191,7 +255,9 @@ class Configuration(configparser.RawConfigParser):
def load(self, path: Path) -> None:
"""
fully load configuration
:param path: path to root configuration file
Args:
path(Path): path to root configuration file
"""
if not path.is_file(): # fallback to the system file
path = self.SYSTEM_CONFIGURATION_PATH
@ -214,7 +280,9 @@ class Configuration(configparser.RawConfigParser):
def load_logging(self, quiet: bool) -> None:
"""
setup logging settings from configuration
:param quiet: force disable any log messages
Args:
quiet(bool): force disable any log messages
"""
try:
path = self.logging_path
@ -229,7 +297,9 @@ class Configuration(configparser.RawConfigParser):
def merge_sections(self, architecture: str) -> None:
"""
merge architecture specific sections into main configuration
:param architecture: repository architecture
Args:
architecture(str): repository architecture
"""
self.architecture = architecture
for section in self.ARCHITECTURE_SPECIFIC_SECTIONS:
@ -259,10 +329,12 @@ class Configuration(configparser.RawConfigParser):
def set_option(self, section: str, option: str, value: Optional[str]) -> None:
"""
set option. Unlike default `configparser.RawConfigParser.set` it also creates section if it does not exist
:param section: section name
:param option: option name
:param value: option value as string in parsable format
set option. Unlike default ``configparser.RawConfigParser.set`` it also creates section if it does not exist
Args:
section(str): section name
option(str): option name
value(Optional[str]): option value as string in parsable format
"""
if not self.has_section(section):
self.add_section(section)

View File

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

View File

@ -20,24 +20,28 @@
from sqlite3 import Connection
from ahriman.core.configuration import Configuration
from ahriman.core.database.data.package_remotes import migrate_package_remotes
from ahriman.core.database.data.package_statuses import migrate_package_statuses
from ahriman.core.database.data.patches import migrate_patches
from ahriman.core.database.data.users import migrate_users_data
from ahriman.models.migration_result import MigrationResult
from ahriman.models.repository_paths import RepositoryPaths
def migrate_data(result: MigrationResult, connection: Connection,
configuration: Configuration, paths: RepositoryPaths) -> None:
def migrate_data(result: MigrationResult, connection: Connection, configuration: Configuration) -> None:
"""
perform data migration
:param result: result of the schema migration
:param connection: database connection
:param configuration: configuration instance
:param paths: repository paths instance
Args:
result(MigrationResult): result of the schema migration
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
# initial data migration
repository_paths = configuration.repository_paths
if result.old_version <= 0:
migrate_package_statuses(connection, paths)
migrate_patches(connection, paths)
migrate_package_statuses(connection, repository_paths)
migrate_patches(connection, repository_paths)
migrate_users_data(connection, configuration)
if result.old_version <= 1:
migrate_package_remotes(connection, repository_paths)

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