Compare commits

...

18 Commits

Author SHA1 Message Date
d495163fdd use autodoc_mock_imports instead of custom mocking for docs 2023-05-31 19:17:16 +03:00
041e9536bf use cached property instead of custom __getattr__ implementation 2023-05-31 19:17:16 +03:00
1625fddccd support check dependencies 2023-05-31 19:17:16 +03:00
8f2c33fe5d mimic parent arguments during spawn process (#99) 2023-05-30 18:09:35 +03:00
d064e46af6 do not terminate child process explicitly
This feature sometimes causes the main process termination. Since the
child process has been already terminated (or going to) there is no need
to terminate it manually
2023-05-29 17:27:04 +03:00
5627c9cca0 fix case where it was impossible to create session if user and password
are required

Issue appears if auth is enabled and unix socket is disabled and caused
by 7f5e541120
2023-05-29 17:23:57 +03:00
8731cee8ea runtime logger handler selector 2023-05-29 03:41:11 +03:00
01a4d68ce6 add repod reference to faq 2023-05-26 12:58:25 +03:00
8f0a6cf6b5 allow to filter packages for rebuild by their statuses 2023-05-25 17:41:54 +03:00
d96e3c97db fix documentation error 2023-05-25 16:31:47 +03:00
1cf027cb55 Support type triggers (#96)
* implement mirrorlist package generator

* implement keyring package generator

* docs update

* do not skip empty lines

* fill remote source for local packages

* faq update
2023-05-25 16:27:16 +03:00
8e0e57e193 switch to explicit journald logger instead of syslog 2023-05-25 04:44:38 +03:00
f855e645cc update optional dependencies list 2023-05-24 15:39:36 +03:00
8ab0df2bed bump rtd image 2023-05-24 03:58:14 +03:00
840921f585 update packaging files
also remove makedependencies after installation in docker
2023-05-22 18:23:06 +03:00
6f566b0c6c Release 2.9.0 2023-05-22 17:04:32 +03:00
d06399db2c update models index 2023-05-22 17:02:55 +03:00
70e9e3e822 migrate to the latest devtools & archlinux git changes
This migration includes
* removal of community repository as it is no more
* fixed paths for devtools configurations
* migration of archlinux packaging git url
2023-05-22 16:51:27 +03:00
113 changed files with 6776 additions and 3947 deletions

View File

@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = http://repo.arcanis.me/$arch\nSigLevel = Never'
# refresh the image # refresh the image
pacman --noconfirm -Syu pacman --noconfirm -Syu
# main dependencies # main dependencies
pacman --noconfirm -Sy base-devel devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo sudo pacman --noconfirm -Sy base-devel devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo python-systemd sudo
# make dependencies # make dependencies
pacman --noconfirm -Sy python-build python-installer python-wheel pacman --noconfirm -Sy python-build python-installer python-wheel
# optional dependencies # optional dependencies

View File

@ -6,7 +6,7 @@ formats:
build: build:
os: ubuntu-20.04 os: ubuntu-20.04
tools: tools:
python: "3.10" python: "3.11"
sphinx: sphinx:
builder: html builder: html

View File

@ -34,7 +34,7 @@ Again, the most checks can be performed by `make check` command, though some add
do foo. With very very very long do foo. With very very very long
docstring docstring
Note: Notes:
Very important note about this function Very important note about this function
Args: Args:
@ -102,6 +102,9 @@ Again, the most checks can be performed by `make check` command, though some add
@property @property
def property(self) -> Any: ... def property(self) -> Any: ...
@cached_property
def property_cached(self) -> Any: ... # cached property has to be treated as normal one
@classmethod @classmethod
def class_method(cls) -> Self: ... def class_method(cls) -> Self: ...

View File

@ -6,7 +6,7 @@ ENV AHRIMAN_DEBUG=""
ENV AHRIMAN_FORCE_ROOT="" ENV AHRIMAN_FORCE_ROOT=""
ENV AHRIMAN_HOST="0.0.0.0" ENV AHRIMAN_HOST="0.0.0.0"
ENV AHRIMAN_MULTILIB="yes" ENV AHRIMAN_MULTILIB="yes"
ENV AHRIMAN_OUTPUT="syslog" ENV AHRIMAN_OUTPUT=""
ENV AHRIMAN_PACKAGER="ahriman bot <ahriman@example.com>" ENV AHRIMAN_PACKAGER="ahriman bot <ahriman@example.com>"
ENV AHRIMAN_PACMAN_MIRROR="" ENV AHRIMAN_PACMAN_MIRROR=""
ENV AHRIMAN_PORT="" ENV AHRIMAN_PORT=""
@ -28,15 +28,12 @@ RUN useradd -m -d "/home/build" -s "/usr/bin/nologin" build && \
COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package" COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
## install package dependencies ## install package dependencies
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size ## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
RUN pacman --noconfirm -Sy devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \ RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \
pacman --noconfirm -Sy python-build python-installer python-wheel && \ pacman -Sy --noconfirm --asdeps python-build python-installer python-wheel && \
pacman --noconfirm -Sy breezy mercurial python-aiohttp python-aiohttp-cors python-boto3 python-cryptography python-jinja python-requests-unixsocket rsync subversion && \ pacman -Sy --noconfirm --asdeps breezy mercurial python-aiohttp python-aiohttp-cors python-boto3 python-cryptography python-jinja python-requests-unixsocket python-systemd rsync subversion && \
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-jinja2 \ runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-jinja2 \
python-aiohttp-debugtoolbar python-aiohttp-session python-aiohttp-security python-aiohttp-debugtoolbar python-aiohttp-session python-aiohttp-security
# cleanup unused
RUN find "/var/cache/pacman/pkg" -type f -delete
# install ahriman # install ahriman
## copy tree ## copy tree
COPY --chown=build . "/home/build/ahriman" COPY --chown=build . "/home/build/ahriman"
@ -48,6 +45,10 @@ RUN cd "/home/build/ahriman" && \
runuser -u build -- makepkg --noconfirm --install --skipchecksums && \ runuser -u build -- makepkg --noconfirm --install --skipchecksums && \
cd / && rm -r "/home/build/ahriman" cd / && rm -r "/home/build/ahriman"
# cleanup unused
RUN find "/var/cache/pacman/pkg" -type f -delete
RUN pacman -Qdtq | pacman -Rscn --noconfirm -
VOLUME ["/var/lib/ahriman"] VOLUME ["/var/lib/ahriman"]
# minimal runtime ahriman setup # minimal runtime ahriman setup

View File

@ -15,15 +15,10 @@ database = $AHRIMAN_REPOSITORY_ROOT/ahriman.db
host = $AHRIMAN_HOST host = $AHRIMAN_HOST
EOF EOF
sed -i "s|handlers = syslog_handler|handlers = ${AHRIMAN_OUTPUT}_handler|g" "/etc/ahriman.ini.d/logging.ini"
AHRIMAN_DEFAULT_ARGS=("--architecture" "$AHRIMAN_ARCHITECTURE") AHRIMAN_DEFAULT_ARGS=("--architecture" "$AHRIMAN_ARCHITECTURE")
if [[ "$AHRIMAN_OUTPUT" == "syslog" ]]; then if [ -n "$AHRIMAN_OUTPUT" ]; then
if [ ! -e "/dev/log" ]; then AHRIMAN_DEFAULT_ARGS+=("--log-handler" "$AHRIMAN_OUTPUT")
# by default ahriman uses syslog which is not available inside container
# to make noise less we force quiet mode in case if /dev/log was not mounted
AHRIMAN_DEFAULT_ARGS+=("--quiet")
fi
fi fi
# create repository root inside the [[mounted]] directory and set correct ownership # create repository root inside the [[mounted]] directory and set correct ownership

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 750 KiB

After

Width:  |  Height:  |  Size: 762 KiB

View File

@ -1,9 +1,9 @@
.TH AHRIMAN "1" "2023\-04\-09" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2023\-05\-28" "ahriman" "Generated Python Manual"
.SH NAME .SH NAME
ahriman ahriman
.SH SYNOPSIS .SH SYNOPSIS
.B ahriman .B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--report | --no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,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,patch-set-add,repo-backup,repo-check,check,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,user-add,user-list,user-remove,web} ... [-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [--report | --no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,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,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,user-add,user-list,user-remove,web} ...
.SH DESCRIPTION .SH DESCRIPTION
ArcH linux ReposItory MANager ArcH linux ReposItory MANager
@ -24,9 +24,13 @@ force run, remove file lock
\fB\-l\fR \fI\,LOCK\/\fR, \fB\-\-lock\fR \fI\,LOCK\/\fR \fB\-l\fR \fI\,LOCK\/\fR, \fB\-\-lock\fR \fI\,LOCK\/\fR
lock file lock file
.TP
\fB\-\-log\-handler\fR \fI\,{console,syslog,journald}\/\fR
explicit log handler specification. If none set, the handler will be guessed from environment
.TP .TP
\fB\-\-report\fR, \fB\-\-no\-report\fR \fB\-\-report\fR, \fB\-\-no\-report\fR
force enable or disable reporting to web service (default: True) force enable or disable reporting to web service
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
@ -91,6 +95,12 @@ backup repository data
\fBahriman\fR \fI\,repo\-check\/\fR \fBahriman\fR \fI\,repo\-check\/\fR
check for updates check for updates
.TP .TP
\fBahriman\fR \fI\,repo\-create\-keyring\/\fR
create keyring package
.TP
\fBahriman\fR \fI\,repo\-create\-mirrorlist\/\fR
create mirrorlist package
.TP
\fBahriman\fR \fI\,repo\-daemon\/\fR \fBahriman\fR \fI\,repo\-daemon\/\fR
run application as daemon run application as daemon
.TP .TP
@ -172,7 +182,7 @@ return non\-zero exit status if result is empty
.TP .TP
\fB\-\-info\fR, \fB\-\-no\-info\fR \fB\-\-info\fR, \fB\-\-no\-info\fR
show additional package information (default: False) show additional package information
.TP .TP
\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,submitter,url,url_path,version}\/\fR \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,submitter,url,url_path,version}\/\fR
@ -228,7 +238,7 @@ package source (base name, path to local files, remote URL)
.SH OPTIONS \fI\,'ahriman package\-add'\/\fR .SH OPTIONS \fI\,'ahriman package\-add'\/\fR
.TP .TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR \fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies (default: True) process missing package dependencies
.TP .TP
\fB\-e\fR, \fB\-\-exit\-code\fR \fB\-e\fR, \fB\-\-exit\-code\fR
@ -276,7 +286,7 @@ return non\-zero exit status if result is empty
.TP .TP
\fB\-\-info\fR, \fB\-\-no\-info\fR \fB\-\-info\fR, \fB\-\-no\-info\fR
show additional package information (default: False) show additional package information
.TP .TP
\fB\-s\fR \fI\,{unknown,pending,building,failed,success}\/\fR, \fB\-\-status\fR \fI\,{unknown,pending,building,failed,success}\/\fR \fB\-s\fR \fI\,{unknown,pending,building,failed,success}\/\fR, \fB\-\-status\fR \fI\,{unknown,pending,building,failed,success}\/\fR
@ -394,12 +404,22 @@ return non\-zero exit status if result is empty
.TP .TP
\fB\-\-vcs\fR, \fB\-\-no\-vcs\fR \fB\-\-vcs\fR, \fB\-\-no\-vcs\fR
fetch actual version of VCS packages (default: True) fetch actual version of VCS packages
.TP .TP
\fB\-y\fR, \fB\-\-refresh\fR \fB\-y\fR, \fB\-\-refresh\fR
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-create\-keyring'\/\fR
usage: ahriman repo\-create\-keyring [\-h]
create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-create\-mirrorlist'\/\fR
usage: ahriman repo\-create\-mirrorlist [\-h]
create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR .SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-dependencies | \-\-no\-dependencies] usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-dependencies | \-\-no\-dependencies]
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-vcs | \-\-no\-vcs] [\-y] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-vcs | \-\-no\-vcs] [\-y]
@ -413,23 +433,23 @@ interval between runs in seconds
.TP .TP
\fB\-\-aur\fR, \fB\-\-no\-aur\fR \fB\-\-aur\fR, \fB\-\-no\-aur\fR
enable or disable checking for AUR updates (default: True) enable or disable checking for AUR updates
.TP .TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR \fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies (default: True) process missing package dependencies
.TP .TP
\fB\-\-local\fR, \fB\-\-no\-local\fR \fB\-\-local\fR, \fB\-\-no\-local\fR
enable or disable checking of local packages for updates (default: True) enable or disable checking of local packages for updates
.TP .TP
\fB\-\-manual\fR, \fB\-\-no\-manual\fR \fB\-\-manual\fR, \fB\-\-no\-manual\fR
include or exclude manual updates (default: True) include or exclude manual updates
.TP .TP
\fB\-\-vcs\fR, \fB\-\-no\-vcs\fR \fB\-\-vcs\fR, \fB\-\-no\-vcs\fR
fetch actual version of VCS packages (default: True) fetch actual version of VCS packages
.TP .TP
\fB\-y\fR, \fB\-\-refresh\fR \fB\-y\fR, \fB\-\-refresh\fR
@ -437,6 +457,7 @@ download fresh package databases from the mirror before actions, \-yy to force r
.SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR .SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-e] usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-e]
[\-s {unknown,pending,building,failed,success}]
force rebuild whole repository force rebuild whole repository
@ -459,6 +480,10 @@ original ahriman instance run with web service and have run repo\-update at leas
\fB\-e\fR, \fB\-\-exit\-code\fR \fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty return non\-zero exit status if result is empty
.TP
\fB\-s\fR \fI\,{unknown,pending,building,failed,success}\/\fR, \fB\-\-status\fR \fI\,{unknown,pending,building,failed,success}\/\fR
filter packages by status. Requires \-\-from\-database to be set
.SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR .SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run] usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
@ -540,11 +565,11 @@ filter check by package base
.SH OPTIONS \fI\,'ahriman repo\-update'\/\fR .SH OPTIONS \fI\,'ahriman repo\-update'\/\fR
.TP .TP
\fB\-\-aur\fR, \fB\-\-no\-aur\fR \fB\-\-aur\fR, \fB\-\-no\-aur\fR
enable or disable checking for AUR updates (default: True) enable or disable checking for AUR updates
.TP .TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR \fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies (default: True) process missing package dependencies
.TP .TP
\fB\-\-dry\-run\fR \fB\-\-dry\-run\fR
@ -556,15 +581,15 @@ return non\-zero exit status if result is empty
.TP .TP
\fB\-\-local\fR, \fB\-\-no\-local\fR \fB\-\-local\fR, \fB\-\-no\-local\fR
enable or disable checking of local packages for updates (default: True) enable or disable checking of local packages for updates
.TP .TP
\fB\-\-manual\fR, \fB\-\-no\-manual\fR \fB\-\-manual\fR, \fB\-\-no\-manual\fR
include or exclude manual updates (default: True) include or exclude manual updates
.TP .TP
\fB\-\-vcs\fR, \fB\-\-no\-vcs\fR \fB\-\-vcs\fR, \fB\-\-no\-vcs\fR
fetch actual version of VCS packages (default: True) fetch actual version of VCS packages
.TP .TP
\fB\-y\fR, \fB\-\-refresh\fR \fB\-y\fR, \fB\-\-refresh\fR
@ -579,23 +604,23 @@ remove local caches
.SH OPTIONS \fI\,'ahriman service\-clean'\/\fR .SH OPTIONS \fI\,'ahriman service\-clean'\/\fR
.TP .TP
\fB\-\-cache\fR, \fB\-\-no\-cache\fR \fB\-\-cache\fR, \fB\-\-no\-cache\fR
clear directory with package caches (default: False) clear directory with package caches
.TP .TP
\fB\-\-chroot\fR, \fB\-\-no\-chroot\fR \fB\-\-chroot\fR, \fB\-\-no\-chroot\fR
clear build chroot (default: False) clear build chroot
.TP .TP
\fB\-\-manual\fR, \fB\-\-no\-manual\fR \fB\-\-manual\fR, \fB\-\-no\-manual\fR
clear manually added packages queue (default: False) clear manually added packages queue
.TP .TP
\fB\-\-packages\fR, \fB\-\-no\-packages\fR \fB\-\-packages\fR, \fB\-\-no\-packages\fR
clear directory with built packages (default: False) clear directory with built packages
.TP .TP
\fB\-\-pacman\fR, \fB\-\-no\-pacman\fR \fB\-\-pacman\fR, \fB\-\-no\-pacman\fR
clear directory with pacman local database cache (default: False) clear directory with pacman local database cache
.SH COMMAND \fI\,'ahriman service\-config'\/\fR .SH COMMAND \fI\,'ahriman service\-config'\/\fR
usage: ahriman service\-config [\-h] [\-\-secure | \-\-no\-secure] usage: ahriman service\-config [\-h] [\-\-secure | \-\-no\-secure]
@ -605,7 +630,7 @@ dump configuration for the specified architecture
.SH OPTIONS \fI\,'ahriman service\-config'\/\fR .SH OPTIONS \fI\,'ahriman service\-config'\/\fR
.TP .TP
\fB\-\-secure\fR, \fB\-\-no\-secure\fR \fB\-\-secure\fR, \fB\-\-no\-secure\fR
hide passwords and secrets from output (default: True) hide passwords and secrets from output
.SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR .SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR
usage: ahriman service\-config\-validate [\-h] [\-e] usage: ahriman service\-config\-validate [\-h] [\-e]
@ -655,7 +680,7 @@ path to default devtools pacman configuration
.TP .TP
\fB\-\-makeflags\-jobs\fR, \fB\-\-no\-makeflags\-jobs\fR \fB\-\-makeflags\-jobs\fR, \fB\-\-no\-makeflags\-jobs\fR
append MAKEFLAGS variable with parallelism set to number of cores (default: True) append MAKEFLAGS variable with parallelism set to number of cores
.TP .TP
\fB\-\-mirror\fR \fI\,MIRROR\/\fR \fB\-\-mirror\fR \fI\,MIRROR\/\fR
@ -663,7 +688,7 @@ use the specified explicitly mirror instead of including mirrorlist
.TP .TP
\fB\-\-multilib\fR, \fB\-\-no\-multilib\fR \fB\-\-multilib\fR, \fB\-\-no\-multilib\fR
add or do not multilib repository (default: True) add or do not multilib repository
.TP .TP
\fB\-\-packager\fR \fI\,PACKAGER\/\fR \fB\-\-packager\fR \fI\,PACKAGER\/\fR
@ -756,7 +781,7 @@ start web server
.SH COMMENTS .SH COMMENTS
Argument list can also be read from file by using @ prefix. Argument list can also be read from file by using @ prefix.
.SH AUTHORS .SH AUTHOR
.nf .nf
ahriman team ahriman team
.fi .fi

View File

@ -60,6 +60,14 @@ ahriman.core.database.migrations.m006\_packages\_architecture\_required module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.migrations.m007\_check\_depends module
------------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m007_check_depends
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -20,6 +20,14 @@ ahriman.core.log.http\_log\_handler module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.log.journal\_handler module
----------------------------------------
.. automodule:: ahriman.core.log.journal_handler
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.log.lazy\_logging module ahriman.core.log.lazy\_logging module
------------------------------------- -------------------------------------

View File

@ -19,6 +19,7 @@ Subpackages
ahriman.core.repository ahriman.core.repository
ahriman.core.sign ahriman.core.sign
ahriman.core.status ahriman.core.status
ahriman.core.support
ahriman.core.triggers ahriman.core.triggers
ahriman.core.upload ahriman.core.upload

View File

@ -0,0 +1,37 @@
ahriman.core.support.pkgbuild package
=====================================
Submodules
----------
ahriman.core.support.pkgbuild.keyring\_generator module
-------------------------------------------------------
.. automodule:: ahriman.core.support.pkgbuild.keyring_generator
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.support.pkgbuild.mirrorlist\_generator module
----------------------------------------------------------
.. automodule:: ahriman.core.support.pkgbuild.mirrorlist_generator
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.support.pkgbuild.pkgbuild\_generator module
--------------------------------------------------------
.. automodule:: ahriman.core.support.pkgbuild.pkgbuild_generator
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.support.pkgbuild
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,45 @@
ahriman.core.support package
============================
Subpackages
-----------
.. toctree::
:maxdepth: 4
ahriman.core.support.pkgbuild
Submodules
----------
ahriman.core.support.keyring\_trigger module
--------------------------------------------
.. automodule:: ahriman.core.support.keyring_trigger
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.support.mirrorlist\_trigger module
-----------------------------------------------
.. automodule:: ahriman.core.support.mirrorlist_trigger
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.support.package\_creator module
--------------------------------------------
.. automodule:: ahriman.core.support.package_creator
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.support
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -60,6 +60,14 @@ ahriman.models.internal\_status module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.models.log\_handler module
----------------------------------
.. automodule:: ahriman.models.log_handler
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.log\_record\_id module ahriman.models.log\_record\_id module
------------------------------------- -------------------------------------
@ -108,6 +116,14 @@ ahriman.models.package\_source module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.models.pacman\_synchronization module
---------------------------------------------
.. automodule:: ahriman.models.pacman_synchronization
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.pkgbuild\_patch module ahriman.models.pkgbuild\_patch module
------------------------------------- -------------------------------------

View File

@ -1,8 +1,8 @@
# AUTOMATICALLY GENERATED by `shtab` # AUTOMATICALLY GENERATED by `shtab`
_shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' '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' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web') _shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' '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' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--report' '--no-report' '-q' '--quiet' '--unsafe' '-V' '--version') _shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '--report' '--no-report' '-q' '--quiet' '--unsafe' '-V' '--version')
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by') _shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
_shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by') _shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
_shtab_ahriman_help_option_strings=('-h' '--help') _shtab_ahriman_help_option_strings=('-h' '--help')
@ -27,10 +27,12 @@ _shtab_ahriman_patch_set_add_option_strings=('-h' '--help' '-t' '--track')
_shtab_ahriman_repo_backup_option_strings=('-h' '--help') _shtab_ahriman_repo_backup_option_strings=('-h' '--help')
_shtab_ahriman_repo_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_repo_create_keyring_option_strings=('-h' '--help')
_shtab_ahriman_repo_create_mirrorlist_option_strings=('-h' '--help')
_shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code') _shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code' '-s' '--status')
_shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code') _shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code' '-s' '--status')
_shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run') _shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run')
_shtab_ahriman_remove_unknown_option_strings=('-h' '--help' '--dry-run') _shtab_ahriman_remove_unknown_option_strings=('-h' '--help' '--dry-run')
_shtab_ahriman_repo_report_option_strings=('-h' '--help') _shtab_ahriman_repo_report_option_strings=('-h' '--help')
@ -70,7 +72,8 @@ _shtab_ahriman_web_option_strings=('-h' '--help')
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' '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' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web') _shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' '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' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
_shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version') _shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
_shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version') _shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
_shtab_ahriman_package_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository') _shtab_ahriman_package_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
@ -87,6 +90,10 @@ _shtab_ahriman_package_status_update__s_choices=('unknown' 'pending' 'building'
_shtab_ahriman_package_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success') _shtab_ahriman_package_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_status_update__s_choices=('unknown' 'pending' 'building' 'failed' 'success') _shtab_ahriman_status_update__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success') _shtab_ahriman_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_repo_rebuild__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_repo_rebuild___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_rebuild__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_rebuild___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_repo_status_update__s_choices=('unknown' 'pending' 'building' 'failed' 'success') _shtab_ahriman_repo_status_update__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_repo_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success') _shtab_ahriman_repo_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
_shtab_ahriman_service_setup___sign_target_choices=('disabled' 'packages' 'repository') _shtab_ahriman_service_setup___sign_target_choices=('disabled' 'packages' 'repository')
@ -230,6 +237,10 @@ _shtab_ahriman_check___vcs_nargs=0
_shtab_ahriman_check___no_vcs_nargs=0 _shtab_ahriman_check___no_vcs_nargs=0
_shtab_ahriman_check__y_nargs=0 _shtab_ahriman_check__y_nargs=0
_shtab_ahriman_check___refresh_nargs=0 _shtab_ahriman_check___refresh_nargs=0
_shtab_ahriman_repo_create_keyring__h_nargs=0
_shtab_ahriman_repo_create_keyring___help_nargs=0
_shtab_ahriman_repo_create_mirrorlist__h_nargs=0
_shtab_ahriman_repo_create_mirrorlist___help_nargs=0
_shtab_ahriman_repo_daemon__h_nargs=0 _shtab_ahriman_repo_daemon__h_nargs=0
_shtab_ahriman_repo_daemon___help_nargs=0 _shtab_ahriman_repo_daemon___help_nargs=0
_shtab_ahriman_repo_daemon___aur_nargs=0 _shtab_ahriman_repo_daemon___aur_nargs=0

View File

@ -36,6 +36,8 @@ _shtab_ahriman_commands() {
"repo-clean:remove local caches" "repo-clean:remove local caches"
"repo-config:dump configuration for the specified architecture" "repo-config:dump configuration for the specified architecture"
"repo-config-validate:validate configuration and print found errors" "repo-config-validate:validate configuration and print found errors"
"repo-create-keyring:create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually"
"repo-create-mirrorlist:create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually"
"repo-daemon:start process which periodically will run update process" "repo-daemon:start process which periodically will run update process"
"repo-init:create initial service configuration, requires root" "repo-init:create initial service configuration, requires root"
"repo-rebuild:force rebuild whole repository" "repo-rebuild:force rebuild whole repository"
@ -79,7 +81,8 @@ _shtab_ahriman_options=(
{-c,--configuration}"[configuration path]:configuration:" {-c,--configuration}"[configuration path]:configuration:"
"--force[force run, remove file lock]" "--force[force run, remove file lock]"
{-l,--lock}"[lock file]:lock:" {-l,--lock}"[lock file]:lock:"
{--report,--no-report}"[force enable or disable reporting to web service (default\: \%(default)s)]:report:" "--log-handler[explicit log handler specification. If none set, the handler will be guessed from environment]:log_handler:(console syslog journald)"
{--report,--no-report}"[force enable or disable reporting to web service]:report:"
{-q,--quiet}"[force disable any logging]" {-q,--quiet}"[force disable any logging]"
"--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable]" "--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable]"
"(- : *)"{-V,--version}"[show program\'s version number and exit]" "(- : *)"{-V,--version}"[show program\'s version number and exit]"
@ -87,7 +90,7 @@ _shtab_ahriman_options=(
_shtab_ahriman_add_options=( _shtab_ahriman_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies]:dependencies:"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-n,--now}"[run update function after]" {-n,--now}"[run update function after]"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
@ -98,7 +101,7 @@ _shtab_ahriman_add_options=(
_shtab_ahriman_aur_search_options=( _shtab_ahriman_aur_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:" {--info,--no-info}"[show additional package information]:info:"
"--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository submitter url url_path version)" "--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository submitter url url_path version)"
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
@ -106,23 +109,23 @@ _shtab_ahriman_aur_search_options=(
_shtab_ahriman_check_options=( _shtab_ahriman_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
"(*)::filter check by package base:" "(*)::filter check by package base:"
) )
_shtab_ahriman_clean_options=( _shtab_ahriman_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: \%(default)s)]:cache:" {--cache,--no-cache}"[clear directory with package caches]:cache:"
{--chroot,--no-chroot}"[clear build chroot (default\: \%(default)s)]:chroot:" {--chroot,--no-chroot}"[clear build chroot]:chroot:"
{--manual,--no-manual}"[clear manually added packages queue (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[clear manually added packages queue]:manual:"
{--packages,--no-packages}"[clear directory with built packages (default\: \%(default)s)]:packages:" {--packages,--no-packages}"[clear directory with built packages]:packages:"
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: \%(default)s)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache]:pacman:"
) )
_shtab_ahriman_config_options=( _shtab_ahriman_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--secure,--no-secure}"[hide passwords and secrets from output (default\: \%(default)s)]:secure:" {--secure,--no-secure}"[hide passwords and secrets from output]:secure:"
) )
_shtab_ahriman_config_validate_options=( _shtab_ahriman_config_validate_options=(
@ -133,11 +136,11 @@ _shtab_ahriman_config_validate_options=(
_shtab_ahriman_daemon_options=( _shtab_ahriman_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds]:interval:" {-i,--interval}"[interval between runs in seconds]:interval:"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies]:dependencies:"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
) )
@ -165,9 +168,9 @@ _shtab_ahriman_init_options=(
"--build-as-user[force makepkg user to the specific one]:build_as_user:" "--build-as-user[force makepkg user to the specific one]:build_as_user:"
"--build-command[build command prefix]:build_command:" "--build-command[build command prefix]:build_command:"
"--from-configuration[path to default devtools pacman configuration]:from_configuration:" "--from-configuration[path to default devtools pacman configuration]:from_configuration:"
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository]:multilib:"
"--packager[packager name and email]:packager:" "--packager[packager name and email]:packager:"
"--repository[repository name]:repository:" "--repository[repository name]:repository:"
"--sign-key[sign key id]:sign_key:" "--sign-key[sign key id]:sign_key:"
@ -184,7 +187,7 @@ _shtab_ahriman_key_import_options=(
_shtab_ahriman_package_add_options=( _shtab_ahriman_package_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies]:dependencies:"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-n,--now}"[run update function after]" {-n,--now}"[run update function after]"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
@ -201,7 +204,7 @@ _shtab_ahriman_package_status_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--ahriman[get service status itself]" "--ahriman[get service status itself]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:" {--info,--no-info}"[show additional package information]:info:"
{-s,--status}"[filter packages by status]:status:(unknown pending building failed success)" {-s,--status}"[filter packages by status]:status:(unknown pending building failed success)"
"(*)::filter status by package base:" "(*)::filter status by package base:"
) )
@ -219,7 +222,7 @@ _shtab_ahriman_package_status_update_options=(
_shtab_ahriman_package_update_options=( _shtab_ahriman_package_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies]:dependencies:"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-n,--now}"[run update function after]" {-n,--now}"[run update function after]"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
@ -259,6 +262,7 @@ _shtab_ahriman_rebuild_options=(
"--dry-run[just perform check for packages without rebuild process itself]" "--dry-run[just perform check for packages without rebuild process itself]"
"--from-database[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.]" "--from-database[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.]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-s,--status}"[filter packages by status. Requires --from-database to be set]:status:(unknown pending building failed success)"
) )
_shtab_ahriman_remove_options=( _shtab_ahriman_remove_options=(
@ -279,23 +283,23 @@ _shtab_ahriman_repo_backup_options=(
_shtab_ahriman_repo_check_options=( _shtab_ahriman_repo_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
"(*)::filter check by package base:" "(*)::filter check by package base:"
) )
_shtab_ahriman_repo_clean_options=( _shtab_ahriman_repo_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: \%(default)s)]:cache:" {--cache,--no-cache}"[clear directory with package caches]:cache:"
{--chroot,--no-chroot}"[clear build chroot (default\: \%(default)s)]:chroot:" {--chroot,--no-chroot}"[clear build chroot]:chroot:"
{--manual,--no-manual}"[clear manually added packages queue (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[clear manually added packages queue]:manual:"
{--packages,--no-packages}"[clear directory with built packages (default\: \%(default)s)]:packages:" {--packages,--no-packages}"[clear directory with built packages]:packages:"
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: \%(default)s)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache]:pacman:"
) )
_shtab_ahriman_repo_config_options=( _shtab_ahriman_repo_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--secure,--no-secure}"[hide passwords and secrets from output (default\: \%(default)s)]:secure:" {--secure,--no-secure}"[hide passwords and secrets from output]:secure:"
) )
_shtab_ahriman_repo_config_validate_options=( _shtab_ahriman_repo_config_validate_options=(
@ -303,14 +307,22 @@ _shtab_ahriman_repo_config_validate_options=(
{-e,--exit-code}"[return non-zero exit status if configuration is invalid]" {-e,--exit-code}"[return non-zero exit status if configuration is invalid]"
) )
_shtab_ahriman_repo_create_keyring_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
)
_shtab_ahriman_repo_create_mirrorlist_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
)
_shtab_ahriman_repo_daemon_options=( _shtab_ahriman_repo_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds]:interval:" {-i,--interval}"[interval between runs in seconds]:interval:"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies]:dependencies:"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
) )
@ -319,9 +331,9 @@ _shtab_ahriman_repo_init_options=(
"--build-as-user[force makepkg user to the specific one]:build_as_user:" "--build-as-user[force makepkg user to the specific one]:build_as_user:"
"--build-command[build command prefix]:build_command:" "--build-command[build command prefix]:build_command:"
"--from-configuration[path to default devtools pacman configuration]:from_configuration:" "--from-configuration[path to default devtools pacman configuration]:from_configuration:"
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository]:multilib:"
"--packager[packager name and email]:packager:" "--packager[packager name and email]:packager:"
"--repository[repository name]:repository:" "--repository[repository name]:repository:"
"--sign-key[sign key id]:sign_key:" "--sign-key[sign key id]:sign_key:"
@ -336,6 +348,7 @@ _shtab_ahriman_repo_rebuild_options=(
"--dry-run[just perform check for packages without rebuild process itself]" "--dry-run[just perform check for packages without rebuild process itself]"
"--from-database[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.]" "--from-database[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.]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-s,--status}"[filter packages by status. Requires --from-database to be set]:status:(unknown pending building failed success)"
) )
_shtab_ahriman_repo_remove_unknown_options=( _shtab_ahriman_repo_remove_unknown_options=(
@ -358,9 +371,9 @@ _shtab_ahriman_repo_setup_options=(
"--build-as-user[force makepkg user to the specific one]:build_as_user:" "--build-as-user[force makepkg user to the specific one]:build_as_user:"
"--build-command[build command prefix]:build_command:" "--build-command[build command prefix]:build_command:"
"--from-configuration[path to default devtools pacman configuration]:from_configuration:" "--from-configuration[path to default devtools pacman configuration]:from_configuration:"
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository]:multilib:"
"--packager[packager name and email]:packager:" "--packager[packager name and email]:packager:"
"--repository[repository name]:repository:" "--repository[repository name]:repository:"
"--sign-key[sign key id]:sign_key:" "--sign-key[sign key id]:sign_key:"
@ -394,13 +407,13 @@ _shtab_ahriman_repo_triggers_options=(
_shtab_ahriman_repo_update_options=( _shtab_ahriman_repo_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies]:dependencies:"
"--dry-run[just perform check for updates, same as check command]" "--dry-run[just perform check for updates, same as check command]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
"(*)::filter check by package base:" "(*)::filter check by package base:"
) )
@ -412,23 +425,23 @@ _shtab_ahriman_report_options=(
_shtab_ahriman_search_options=( _shtab_ahriman_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:" {--info,--no-info}"[show additional package information]:info:"
"--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository submitter url url_path version)" "--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository submitter url url_path version)"
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
_shtab_ahriman_service_clean_options=( _shtab_ahriman_service_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: \%(default)s)]:cache:" {--cache,--no-cache}"[clear directory with package caches]:cache:"
{--chroot,--no-chroot}"[clear build chroot (default\: \%(default)s)]:chroot:" {--chroot,--no-chroot}"[clear build chroot]:chroot:"
{--manual,--no-manual}"[clear manually added packages queue (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[clear manually added packages queue]:manual:"
{--packages,--no-packages}"[clear directory with built packages (default\: \%(default)s)]:packages:" {--packages,--no-packages}"[clear directory with built packages]:packages:"
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: \%(default)s)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache]:pacman:"
) )
_shtab_ahriman_service_config_options=( _shtab_ahriman_service_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--secure,--no-secure}"[hide passwords and secrets from output (default\: \%(default)s)]:secure:" {--secure,--no-secure}"[hide passwords and secrets from output]:secure:"
) )
_shtab_ahriman_service_config_validate_options=( _shtab_ahriman_service_config_validate_options=(
@ -447,9 +460,9 @@ _shtab_ahriman_service_setup_options=(
"--build-as-user[force makepkg user to the specific one]:build_as_user:" "--build-as-user[force makepkg user to the specific one]:build_as_user:"
"--build-command[build command prefix]:build_command:" "--build-command[build command prefix]:build_command:"
"--from-configuration[path to default devtools pacman configuration]:from_configuration:" "--from-configuration[path to default devtools pacman configuration]:from_configuration:"
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository]:multilib:"
"--packager[packager name and email]:packager:" "--packager[packager name and email]:packager:"
"--repository[repository name]:repository:" "--repository[repository name]:repository:"
"--sign-key[sign key id]:sign_key:" "--sign-key[sign key id]:sign_key:"
@ -468,9 +481,9 @@ _shtab_ahriman_setup_options=(
"--build-as-user[force makepkg user to the specific one]:build_as_user:" "--build-as-user[force makepkg user to the specific one]:build_as_user:"
"--build-command[build command prefix]:build_command:" "--build-command[build command prefix]:build_command:"
"--from-configuration[path to default devtools pacman configuration]:from_configuration:" "--from-configuration[path to default devtools pacman configuration]:from_configuration:"
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository]:multilib:"
"--packager[packager name and email]:packager:" "--packager[packager name and email]:packager:"
"--repository[repository name]:repository:" "--repository[repository name]:repository:"
"--sign-key[sign key id]:sign_key:" "--sign-key[sign key id]:sign_key:"
@ -493,7 +506,7 @@ _shtab_ahriman_status_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--ahriman[get service status itself]" "--ahriman[get service status itself]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:" {--info,--no-info}"[show additional package information]:info:"
{-s,--status}"[filter packages by status]:status:(unknown pending building failed success)" {-s,--status}"[filter packages by status]:status:(unknown pending building failed success)"
"(*)::filter status by package base:" "(*)::filter status by package base:"
) )
@ -510,13 +523,13 @@ _shtab_ahriman_sync_options=(
_shtab_ahriman_update_options=( _shtab_ahriman_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies]:dependencies:"
"--dry-run[just perform check for updates, same as check command]" "--dry-run[just perform check for updates, same as check command]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
"(*)::filter check by package base:" "(*)::filter check by package base:"
) )
@ -595,6 +608,8 @@ _shtab_ahriman() {
repo-clean) _arguments -C $_shtab_ahriman_repo_clean_options ;; repo-clean) _arguments -C $_shtab_ahriman_repo_clean_options ;;
repo-config) _arguments -C $_shtab_ahriman_repo_config_options ;; repo-config) _arguments -C $_shtab_ahriman_repo_config_options ;;
repo-config-validate) _arguments -C $_shtab_ahriman_repo_config_validate_options ;; repo-config-validate) _arguments -C $_shtab_ahriman_repo_config_validate_options ;;
repo-create-keyring) _arguments -C $_shtab_ahriman_repo_create_keyring_options ;;
repo-create-mirrorlist) _arguments -C $_shtab_ahriman_repo_create_mirrorlist_options ;;
repo-daemon) _arguments -C $_shtab_ahriman_repo_daemon_options ;; repo-daemon) _arguments -C $_shtab_ahriman_repo_daemon_options ;;
repo-init) _arguments -C $_shtab_ahriman_repo_init_options ;; repo-init) _arguments -C $_shtab_ahriman_repo_init_options ;;
repo-rebuild) _arguments -C $_shtab_ahriman_repo_rebuild_options ;; repo-rebuild) _arguments -C $_shtab_ahriman_repo_rebuild_options ;;

View File

@ -14,7 +14,6 @@ import os
import sys import sys
from pathlib import Path from pathlib import Path
from unittest import mock
from ahriman.version import __version__ from ahriman.version import __version__
@ -24,13 +23,6 @@ sys.path.insert(0, str(basedir))
on_rtd = os.environ.get("READTHEDOCS", None) == "True" on_rtd = os.environ.get("READTHEDOCS", None) == "True"
for module in (
"pyalpm",
):
if module in sys.modules:
continue
sys.modules[module] = mock.Mock()
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
@ -92,6 +84,8 @@ autoclass_content = "both"
autodoc_member_order = "groupwise" autodoc_member_order = "groupwise"
autodoc_mock_imports = ["pyalpm"]
autodoc_default_options = { autodoc_default_options = {
"no-undoc-members": True, "no-undoc-members": True,
} }

View File

@ -30,8 +30,8 @@ Base configuration settings.
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference. * ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
* ``suppress_http_log_errors`` - suppress http log errors, boolean, optional, default ``no``. If set to ``yes``, any http log errors (e.g. if web server is not available, but http logging is enabled) will be suppressed. * ``suppress_http_log_errors`` - suppress http log errors, boolean, optional, default ``no``. If set to ``yes``, any http log errors (e.g. if web server is not available, but http logging is enabled) will be suppressed.
``alpm`` group ``alpm:*`` groups
-------------- -----------------
libalpm and AUR related configuration. Group name can refer to architecture, e.g. ``alpm:x86_64`` can be used for x86_64 architecture specific settings. libalpm and AUR related configuration. Group name can refer to architecture, e.g. ``alpm:x86_64`` can be used for x86_64 architecture specific settings.
@ -69,6 +69,7 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, 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. * ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
* ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (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. * ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (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.
* ``triggers_known`` - optional list of ``ahriman.core.triggers.Trigger`` class implementations which are not run automatically and used only for trigger discovery and configuration validation.
* ``vcs_allowed_age`` - maximal age in seconds of the VCS packages before their version will be updated with its remote source, int, optional, default ``604800``. * ``vcs_allowed_age`` - maximal age in seconds of the VCS packages before their version will be updated with its remote source, int, optional, default ``604800``.
``repository`` group ``repository`` group
@ -107,6 +108,41 @@ Web server settings. If any of ``host``/``port`` is not set, web integration wil
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration. * ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled. * ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
``keyring`` group
--------------------
Keyring package generator plugin.
* ``target`` - list of generator settings sections, space separated list of strings, required. It must point to valid section name.
Keyring generator plugin
^^^^^^^^^^^^^^^^^^^^^^^^
* ``description`` - keyring package description, string, optional, default is ``repo PGP keyring``, where ``repo`` is the repository name.
* ``homepage`` - url to homepage location if any, string, optional.
* ``license`` - list of licenses which are applied to this package, space separated list of strings, optional, default is ``Unlicense``.
* ``package`` - keyring package name, string, optional, default is ``repo-keyring``, where ``repo`` is the repository name.
* ``packagers`` - list of packagers keys, space separated list of strings, optional, if not set, the ``key_*`` options from ``sign`` group will be used.
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
``mirrorlist`` group
--------------------
Mirrorlist package generator plugin.
* ``target`` - list of generator settings sections, space separated list of strings, required. It must point to valid section name.
Mirrorlist generator plugin
^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``description`` - mirrorlist package description, string, optional, default is ``repo mirror list for use by pacman``, where ``repo`` is the repository name.
* ``homepage`` - url to homepage location if any, string, optional.
* ``license`` - list of licenses which are applied to this package, space separated list of strings, optional, default is ``Unlicense``.
* ``package`` - mirrorlist package name, string, optional, default is ``repo-mirrorlist``, where ``repo`` is the repository name.
* ``path`` - absolute path to generated mirrorlist file, string, optional, default is ``/etc/pacman.d/repo-mirrorlist``, where ``repo`` is the repository name.
* ``servers`` - list of repository mirrors, space separated list of strings, required.
``remote-pull`` group ``remote-pull`` group
--------------------- ---------------------

View File

@ -1,4 +1,3 @@
FAQ FAQ
=== ===
@ -392,7 +391,7 @@ The following environment variables are supported:
* ``AHRIMAN_FORCE_ROOT`` - force run ahriman as root instead of guessing by subcommand. * ``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_HOST`` - host for the web interface, default is ``0.0.0.0``.
* ``AHRIMAN_MULTILIB`` - if set (default) multilib repository will be used, disabled otherwise. * ``AHRIMAN_MULTILIB`` - if set (default) multilib repository will be used, disabled otherwise.
* ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` (the default) handler is used you will need to mount ``/dev/log`` inside container because it is not available there. * ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` 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_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot <ahriman@example.com>``.
* ``AHRIMAN_PACMAN_MIRROR`` - override pacman mirror server if set. * ``AHRIMAN_PACMAN_MIRROR`` - override pacman mirror server if set.
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty. * ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
@ -664,7 +663,7 @@ How to report by email
.. code-block:: shell .. code-block:: shell
yay -S python-jinja yay -S --asdeps python-jinja
#. #.
Configure the service: Configure the service:
@ -691,7 +690,7 @@ How to generate index page for S3
.. code-block:: shell .. code-block:: shell
yay -S python-jinja yay -S --asdeps python-jinja
#. #.
Configure the service: Configure the service:
@ -715,7 +714,7 @@ How to post build report to telegram
.. code-block:: shell .. code-block:: shell
yay -S python-jinja yay -S --asdeps 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>`_. 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>`_.
@ -754,6 +753,80 @@ If you did everything fine you should receive the message with the next update.
(replace ``${CHAT_ID}`` and ``${API_KEY}`` with the values from configuration). (replace ``${CHAT_ID}`` and ``${API_KEY}`` with the values from configuration).
Maintenance packages
--------------------
Generate keyring package
^^^^^^^^^^^^^^^^^^^^^^^^
The application provides special plugin which generates keyring package. This plugin heavily depends on ``sign`` group settings, however it is possible to override them. The minimal package can be generated in the following way:
#.
Edit configuration:
.. code-block:: ini
[keyring]
target = keyring_generator
By default it will use ``sign.key`` as trusted key and all other keys as packagers ones. For all available options refer to :doc:`configuration <configuration>`.
#.
Create package source files:
.. code-block:: shell
sudo -u ahriman ahriman repo-create-keyring
This command will generate PKGBUILD, revoked and trusted listings and keyring itself and will register the package in database.
#.
Build new package as usual:
.. code-block:: shell
sudo -u ahriman ahriman package-add aur-clone-keyring --source local --now
where ``aur-clone`` is your repository name.
This plugin might have some issues, in case of any of them, kindly create `new issue <https://github.com/arcan1s/ahriman/issues/new/choose>`_.
Generate mirrorlist package
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The application provides special plugin which generates mirrorlist package also. It is possible to distribute this package as usual later. The package can be generated in the following way:
#.
Edit configuration:
.. code-block:: ini
[mirrorlist]
target = mirrorlist_generator
[mirrorlist_generator]
servers = https://repo.example.com/$arch
The ``mirrorlist_generator.servers`` must contain list of available mirrors, the ``$arch`` and ``$repo`` variables are supported. For more options kindly refer to :doc:`configuration <configuration>`.
#.
Create package source files:
.. code-block:: shell
sudo -u ahriman ahriman repo-create-mirrorlist
This command will generate PKGBUILD and mirrorlist file and will register the package in database.
#.
Build new package as usual:
.. code-block:: shell
sudo -u ahriman ahriman package-add aur-clone-mirrorlist --source local --now
where ``aur-clone`` is your repository name.
Web service Web service
----------- -----------
@ -765,7 +838,7 @@ How to setup web service
.. code-block:: shell .. code-block:: shell
yay -S python-aiohttp python-aiohttp-jinja2 yay -S --asdeps python-aiohttp python-aiohttp-jinja2 python-aiohttp-apispec>=3.0.0 python-aiohttp-cors
#. #.
Configure service: Configure service:
@ -786,7 +859,7 @@ How to enable basic authorization
.. code-block:: shell .. code-block:: shell
yay -S python-aiohttp-security python-aiohttp-session python-cryptography yay -S --asdeps python-aiohttp-security python-aiohttp-session python-cryptography
#. #.
Configure the service to enable authorization: Configure the service to enable authorization:
@ -842,7 +915,7 @@ How to enable OAuth authorization
.. code-block:: shell .. code-block:: shell
yay -S python-aiohttp-security python-aiohttp-session python-cryptography python-aioauth-client yay -S --asdeps python-aiohttp-security python-aiohttp-session python-cryptography python-aioauth-client
#. #.
Configure the service: Configure the service:
@ -954,6 +1027,11 @@ Don't know, haven't tried it. But it lacks of documentation at least.
* ``repoctl`` is able to store old packages. * ``repoctl`` is able to store old packages.
* Ability to host repository from same command in ``repoctl`` vs external services (e.g. nginx) in ``ahriman``. * Ability to host repository from same command in ``repoctl`` vs external services (e.g. nginx) in ``ahriman``.
`repod <https://gitlab.archlinux.org/archlinux/repod>`_
"""""""""""""""""""""""""""""""""""""""""""""""""""""""
Official tool provided by distribution, has clean logic, but it is just a helper for ``repo-add``, e.g. it doesn't work with AUR and all packages builds have to be handled separately.
`repo-scripts <https://github.com/arcan1s/repo-scripts>`_ `repo-scripts <https://github.com/arcan1s/repo-scripts>`_
""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""
@ -975,13 +1053,17 @@ It is automation tools for ``repoctl`` mentioned above. Except for using shell i
How to check service logs How 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). By default, the service writes logs to ``journald`` which can be accessed by using ``journalctl`` command (logs are written to the journal of the user under which command is run). In order to retrieve logs for the process you can use the following command:
You can also edit configuration and forward logs to ``stderr``, just change ``handlers`` value, e.g.:
.. code-block:: shell .. code-block:: shell
sed -i 's/handlers = syslog_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini sudo journalctl SYSLOG_IDENTIFIER=ahriman
You can also ask to forward logs to ``stderr``, just set ``--log-handler`` flag, e.g.:
.. code-block:: shell
ahriman --log-handler console ...
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>`_. The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger). 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>`_. The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger).

View File

@ -32,11 +32,11 @@ Initial setup
ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build
#. #.
Create configuration file (same as previous ``pacman-{name}.conf``): Create configuration file (same as previous ``{name}.conf``):
.. code-block:: shell .. code-block:: shell
cp /usr/share/devtools/pacman-{extra,ahriman}.conf cp /usr/share/devtools/pacman.conf.d/{extra,ahriman}.conf
#. #.
Change configuration file, add your own repository, add multilib repository etc: Change configuration file, add your own repository, add multilib repository etc:
@ -44,11 +44,11 @@ Initial setup
.. code-block:: shell .. code-block:: shell
echo '[multilib]' | tee -a /usr/share/devtools/pacman-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 'Include = /etc/pacman.d/mirrorlist' | tee -a /usr/share/devtools/pacman.conf.d/ahriman.conf
echo '[aur-clone]' | 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 'SigLevel = Optional TrustAll' | tee -a /usr/share/devtools/pacman.conf.d/ahriman.conf
echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/share/devtools/pacman-ahriman.conf echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/share/devtools/pacman.conf.d/ahriman.conf
#. #.
Set ``build_command`` option to point to your command: Set ``build_command`` option to point to your command:

View File

@ -31,6 +31,16 @@ This trigger will be called right after build process (``on_result``). It will p
Trigger which can be used for reporting. It implements ``on_result`` method and thus being called on each build update and generates report (e.g. html, telegram etc) according to the current settings. Trigger which can be used for reporting. It implements ``on_result`` method and thus being called on each build update and generates report (e.g. html, telegram etc) according to the current settings.
``ahriman.core.support.KeyringTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Generator for keyring package. This trigger will extract keys from local keychain and pack them into keyring specific format. This trigger will generate sources including PKGBUILD, which can be used later for package building.
``ahriman.core.support.MirrorlistTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Simple generator for mirrorlist package, based on the urls which were set by configuration. This trigger will generate sources including PKGBUILD, which can be used later for package building.
``ahriman.core.upload.UploadTrigger`` ``ahriman.core.upload.UploadTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -73,7 +83,7 @@ The ``context`` also implements collection methods such as ``__iter__`` and ``__
Trigger example Trigger example
--------------- ---------------
Lets consider example of reporting trigger (e.g. `slack <https://slack.com/>`_, which provides easy HTTP API for integration triggers).gre Lets consider example of reporting trigger (e.g. `slack <https://slack.com/>`_, which provides easy HTTP API for integration triggers).
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``). 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``).

View File

@ -1,13 +1,13 @@
# Maintainer: Evgeniy Alekseev # Maintainer: Evgeniy Alekseev
pkgname='ahriman' pkgname='ahriman'
pkgver=2.8.0 pkgver=2.9.0
pkgrel=1 pkgrel=1
pkgdesc="ArcH linux ReposItory MANager" pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')
url="https://github.com/arcan1s/ahriman" url="https://github.com/arcan1s/ahriman"
license=('GPL3') license=('GPL3')
depends=('devtools' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo') depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo')
makedepends=('python-build' 'python-installer' 'python-wheel') makedepends=('python-build' 'python-installer' 'python-wheel')
optdepends=('breezy: -bzr packages support' optdepends=('breezy: -bzr packages support'
'darcs: -darcs packages support' 'darcs: -darcs packages support'
@ -24,11 +24,13 @@ optdepends=('breezy: -bzr packages support'
'python-cryptography: web server with authorization' 'python-cryptography: web server with authorization'
'python-requests-unixsocket: client report to web server by unix socket' 'python-requests-unixsocket: client report to web server by unix socket'
'python-jinja: html report generation' 'python-jinja: html report generation'
'python-systemd: journal support'
'rsync: sync by using rsync' 'rsync: sync by using rsync'
'subversion: -svn packages support') 'subversion: -svn packages support')
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz" source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz"
'ahriman.sysusers' 'ahriman.sysusers'
'ahriman.tmpfiles') 'ahriman.tmpfiles')
install="$pkgname.install"
backup=('etc/ahriman.ini' backup=('etc/ahriman.ini'
'etc/ahriman.ini.d/logging.ini') 'etc/ahriman.ini.d/logging.ini')
@ -52,6 +54,6 @@ package() {
install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf" install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf"
} }
sha512sums=('112b0d8aac68e5330bbdd2b86a59c8a9af8ab7a7c636489623c8460bb90f1318585851edd2a97a8ce20e2d2ad93b847b522685df707c190aa39d23ab908fa8ef' sha512sums=('19841842641520b573cdde6cb80a7cfcd69756d323fdfeebc2eee2d264a1325ead4ab2f8383bb369f7896bfc1de59d7358f133f4afeb90a9b9f0695f482a58d0'
'53d37efec812afebf86281716259f9ea78a307b83897166c72777251c3eebcb587ecee375d907514781fb2a5c808cbb24ef9f3f244f12740155d0603bf213131' '53d37efec812afebf86281716259f9ea78a307b83897166c72777251c3eebcb587ecee375d907514781fb2a5c808cbb24ef9f3f244f12740155d0603bf213131'
'62b2eccc352d33853ef243c9cddd63663014aa97b87242f1b5bc5099a7dbd69ff3821f24ffc58e1b7f2387bd4e9e9712cc4c67f661b1724ad99cdf09b3717794') '62b2eccc352d33853ef243c9cddd63663014aa97b87242f1b5bc5099a7dbd69ff3821f24ffc58e1b7f2387bd4e9e9712cc4c67f661b1724ad99cdf09b3717794')

View File

@ -0,0 +1,25 @@
post_upgrade() {
local breakpoints=(
2.9.0-1
)
for v in "${breakpoints[@]}"; do
if [[ $(vercmp "$v" "$2") -eq 1 ]]; then
"_${v//[.-]/_}_changes"
fi
done
}
_2_9_0_1_changes() {
cat << EOF
It was found that you were upgrading from old-devtools package to the new one, which requires manual intervention:
* make sure that devtools are upgraded to the latest release;
* merge service configuration if required;
* run setup command (i.e. sudo ahriman service-setup) with the same arguments as you did before;
* remove build chroot: sudo rm -r /var/lib/ahriman/chroot/ahriman-x86_64/;
* update local databases: sudo -u ahriman ahriman update --no-aur --no-local --no-manual -yy;
For more information kindly refer to changelog https://github.com/arcan1s/ahriman/releases/tag/2.9.0
EOF
}

View File

@ -1 +1 @@
u ahriman 643 "ArcH linux ReposItory MANager" /var/lib/ahriman u ahriman 643 "ArcH linux ReposItory MANager" /var/lib/ahriman

View File

@ -1,2 +1 @@
d /var/lib/ahriman 0755 ahriman ahriman d /var/lib/ahriman 0755 ahriman ahriman
d /var/log/ahriman 0755 ahriman ahriman

View File

@ -7,7 +7,7 @@ suppress_http_log_errors = yes
[alpm] [alpm]
database = /var/lib/pacman database = /var/lib/pacman
mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch
repositories = core extra community multilib repositories = core extra multilib
root = / root = /
use_ahriman_cache = yes use_ahriman_cache = yes
@ -25,6 +25,7 @@ ignore_packages =
makechrootpkg_flags = makechrootpkg_flags =
makepkg_flags = --nocolor --ignorearch makepkg_flags = --nocolor --ignorearch
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger
triggers_known = ahriman.core.gitremote.RemotePullTrigger ahriman.core.gitremote.RemotePushTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.support.KeyringTrigger ahriman.core.support.MirrorlistTrigger
vcs_allowed_age = 604800 vcs_allowed_age = 604800
[repository] [repository]
@ -34,6 +35,12 @@ root = /var/lib/ahriman
[sign] [sign]
target = target =
[keyring]
target =
[mirrorlist]
target =
[remote-pull] [remote-pull]
target = target =

View File

@ -2,17 +2,23 @@
keys = root,http,stderr,boto3,botocore,nose,s3transfer keys = root,http,stderr,boto3,botocore,nose,s3transfer
[handlers] [handlers]
keys = console_handler,syslog_handler keys = console_handler,journald_handler,syslog_handler
[formatters] [formatters]
keys = generic_format,syslog_format keys = generic_format,syslog_format
[handler_console_handler] [handler_console_handler]
class = StreamHandler class = logging.StreamHandler
level = DEBUG level = DEBUG
formatter = generic_format formatter = generic_format
args = (sys.stderr,) args = (sys.stderr,)
[handler_journald_handler]
class = ahriman.core.log.journal_handler.JournalHandler
level = DEBUG
formatter = syslog_format
kwargs = {"SYSLOG_IDENTIFIER": "ahriman"}
[handler_syslog_handler] [handler_syslog_handler]
class = logging.handlers.SysLogHandler class = logging.handlers.SysLogHandler
level = DEBUG level = DEBUG
@ -21,20 +27,16 @@ args = ("/dev/log",)
[formatter_generic_format] [formatter_generic_format]
format = [%(levelname)s %(asctime)s] [%(name)s]: %(message)s format = [%(levelname)s %(asctime)s] [%(name)s]: %(message)s
datefmt =
[formatter_syslog_format] [formatter_syslog_format]
format = [%(levelname)s] [%(name)s]: %(message)s format = [%(levelname)s] [%(name)s]: %(message)s
datefmt =
[logger_root] [logger_root]
level = DEBUG level = DEBUG
handlers = syslog_handler
qualname = root qualname = root
[logger_http] [logger_http]
level = DEBUG level = DEBUG
handlers = syslog_handler
qualname = http qualname = http
propagate = 0 propagate = 0
@ -45,24 +47,20 @@ qualname = stderr
[logger_boto3] [logger_boto3]
level = INFO level = INFO
handlers = syslog_handler
qualname = boto3 qualname = boto3
propagate = 0 propagate = 0
[logger_botocore] [logger_botocore]
level = INFO level = INFO
handlers = syslog_handler
qualname = botocore qualname = botocore
propagate = 0 propagate = 0
[logger_nose] [logger_nose]
level = INFO level = INFO
handlers = syslog_handler
qualname = nose qualname = nose
propagate = 0 propagate = 0
[logger_s3transfer] [logger_s3transfer]
level = INFO level = INFO
handlers = syslog_handler
qualname = s3transfer qualname = s3transfer
propagate = 0 propagate = 0

View File

@ -121,6 +121,9 @@ setup(
"sphinx-rtd-theme>=1.1.1", # https://stackoverflow.com/a/74355734 "sphinx-rtd-theme>=1.1.1", # https://stackoverflow.com/a/74355734
"sphinxcontrib-napoleon", "sphinxcontrib-napoleon",
], ],
"journald": [
"systemd-python",
],
# FIXME technically this dependency is required, but in some cases we do not have access to # 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 # 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 # really need to run the application we can move it to "optional" dependencies

View File

@ -30,6 +30,7 @@ from ahriman.application import handlers
from ahriman.core.util import enum_values from ahriman.core.util import enum_values
from ahriman.models.action import Action from ahriman.models.action import Action
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.log_handler import LogHandler
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.sign_settings import SignSettings from ahriman.models.sign_settings import SignSettings
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
@ -58,6 +59,7 @@ def _formatter(prog: str) -> argparse.HelpFormatter:
return argparse.ArgumentDefaultsHelpFormatter(prog, width=120) return argparse.ArgumentDefaultsHelpFormatter(prog, width=120)
# pylint: disable=too-many-statements
def _parser() -> argparse.ArgumentParser: def _parser() -> argparse.ArgumentParser:
""" """
command line parser generator command line parser generator
@ -70,10 +72,14 @@ def _parser() -> argparse.ArgumentParser:
fromfile_prefix_chars="@", formatter_class=_formatter) fromfile_prefix_chars="@", formatter_class=_formatter)
parser.add_argument("-a", "--architecture", help="target architectures. For several subcommands it can be used " parser.add_argument("-a", "--architecture", help="target architectures. For several subcommands it can be used "
"multiple times", action="append") "multiple times", action="append")
parser.add_argument("-c", "--configuration", help="configuration path", type=Path, default=Path("/etc/ahriman.ini")) parser.add_argument("-c", "--configuration", help="configuration path", type=Path,
default=Path("/etc") / "ahriman.ini")
parser.add_argument("--force", help="force run, remove file lock", action="store_true") parser.add_argument("--force", help="force run, remove file lock", action="store_true")
parser.add_argument("-l", "--lock", help="lock file", type=Path, parser.add_argument("-l", "--lock", help="lock file", type=Path,
default=Path(tempfile.gettempdir()) / "ahriman.lock") default=Path(tempfile.gettempdir()) / "ahriman.lock")
parser.add_argument("--log-handler", help="explicit log handler specification. If none set, the handler will be "
"guessed from environment",
type=LogHandler, choices=enum_values(LogHandler))
parser.add_argument("--report", help="force enable or disable reporting to web service", parser.add_argument("--report", help="force enable or disable reporting to web service",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true") parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
@ -99,6 +105,8 @@ def _parser() -> argparse.ArgumentParser:
_set_patch_set_add_parser(subparsers) _set_patch_set_add_parser(subparsers)
_set_repo_backup_parser(subparsers) _set_repo_backup_parser(subparsers)
_set_repo_check_parser(subparsers) _set_repo_check_parser(subparsers)
_set_repo_create_keyring_parser(subparsers)
_set_repo_create_mirrorlist_parser(subparsers)
_set_repo_daemon_parser(subparsers) _set_repo_daemon_parser(subparsers)
_set_repo_rebuild_parser(subparsers) _set_repo_rebuild_parser(subparsers)
_set_repo_remove_unknown_parser(subparsers) _set_repo_remove_unknown_parser(subparsers)
@ -477,6 +485,44 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser return parser
def _set_repo_create_keyring_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for create-keyring subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-create-keyring", help="create keyring package",
description="create package which contains list of trusted keys as set by "
"configuration. Note, that this action will only create package, the package "
"itself has to be built manually",
formatter_class=_formatter)
parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.support.KeyringTrigger"])
return parser
def _set_repo_create_mirrorlist_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for create-mirrorlist subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-create-mirrorlist", help="create mirrorlist package",
description="create package which contains list of available mirrors as set by "
"configuration. Note, that this action will only create package, the package "
"itself has to be built manually",
formatter_class=_formatter)
parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.support.MirrorlistTrigger"])
return parser
def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for daemon subcommand add parser for daemon subcommand
@ -530,6 +576,8 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
"ahriman instance run with web service and have run repo-update at least once.", "ahriman instance run with web service and have run repo-update at least once.",
action="store_true") action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-s", "--status", help="filter packages by status. Requires --from-database to be set",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
parser.set_defaults(handler=handlers.Rebuild) parser.set_defaults(handler=handlers.Rebuild)
return parser return parser
@ -822,7 +870,7 @@ def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--build-as-user", help="force makepkg user to the specific one") parser.add_argument("--build-as-user", help="force makepkg user to the specific one")
parser.add_argument("--build-command", help="build command prefix", default="ahriman") parser.add_argument("--build-command", help="build command prefix", default="ahriman")
parser.add_argument("--from-configuration", help="path to default devtools pacman configuration", parser.add_argument("--from-configuration", help="path to default devtools pacman configuration",
type=Path, default=Path("/usr/share/devtools/pacman-extra.conf")) type=Path, default=Path("/usr") / "share" / "devtools" / "pacman.conf.d" / "extra.conf")
parser.add_argument("--makeflags-jobs", help="append MAKEFLAGS variable with parallelism set to number of cores", parser.add_argument("--makeflags-jobs", help="append MAKEFLAGS variable with parallelism set to number of cores",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist") parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist")

View File

@ -26,6 +26,7 @@ from typing import Any
from ahriman.application.application.application_properties import ApplicationProperties from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.util import package_like from ahriman.core.util import package_like
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
@ -43,8 +44,14 @@ class ApplicationPackages(ApplicationProperties):
Args: Args:
source(str): path to package archive source(str): path to package archive
Raises:
UnknownPackageError: if specified path doesn't exist
""" """
local_path = Path(source) local_path = Path(source)
if not local_path.is_file():
raise UnknownPackageError(source)
dst = self.repository.paths.packages / local_path.name dst = self.repository.paths.packages / local_path.name
shutil.copy(local_path, dst) shutil.copy(local_path, dst)
@ -68,6 +75,9 @@ class ApplicationPackages(ApplicationProperties):
source(str): path to local directory source(str): path to local directory
""" """
local_dir = Path(source) local_dir = Path(source)
if not local_dir.is_dir():
raise UnknownPackageError(source)
for full_path in filter(package_like, local_dir.iterdir()): for full_path in filter(package_like, local_dir.iterdir()):
self._add_archive(str(full_path)) self._add_archive(str(full_path))
@ -77,12 +87,19 @@ class ApplicationPackages(ApplicationProperties):
Args: Args:
source(str): path to directory with local source files source(str): path to directory with local source files
Raises:
UnknownPackageError: if specified package is unknown or doesn't exist
""" """
source_dir = Path(source) if (source_dir := Path(source)).is_dir():
package = Package.from_build(source_dir, self.architecture) package = Package.from_build(source_dir, self.architecture)
cache_dir = self.repository.paths.cache_for(package.base) cache_dir = self.repository.paths.cache_for(package.base)
shutil.copytree(source_dir, 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 Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
elif (source_dir := self.repository.paths.cache_for(source)).is_dir():
package = Package.from_build(source_dir, self.architecture)
else:
raise UnknownPackageError(source)
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
@ -95,8 +112,11 @@ class ApplicationPackages(ApplicationProperties):
""" """
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it? dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
# timeout=None to suppress pylint warns. Also suppress bandit warnings # timeout=None to suppress pylint warns. Also suppress bandit warnings
response = requests.get(source, stream=True, timeout=None) # nosec try:
response.raise_for_status() response = requests.get(source, stream=True, timeout=None) # nosec
response.raise_for_status()
except Exception:
raise UnknownPackageError(source)
with dst.open("wb") as local_file: with dst.open("wb") as local_file:
for chunk in response.iter_content(chunk_size=1024): for chunk in response.iter_content(chunk_size=1024):

View File

@ -94,7 +94,8 @@ class Handler:
""" """
try: try:
configuration = Configuration.from_path(args.configuration, architecture) configuration = Configuration.from_path(args.configuration, architecture)
Log.load(configuration, quiet=args.quiet, report=args.report) log_handler = Log.handler(args.log_handler)
Log.load(configuration, log_handler, quiet=args.quiet, report=args.report)
with Lock(args, architecture, configuration): with Lock(args, architecture, configuration):
cls.run(args, architecture, configuration, report=args.report, unsafe=args.unsafe) cls.run(args, architecture, configuration, report=args.report, unsafe=args.unsafe)
return True return True

View File

@ -23,6 +23,7 @@ from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.formatters import UpdatePrinter from ahriman.core.formatters import UpdatePrinter
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
@ -47,7 +48,7 @@ class Rebuild(Handler):
application = Application(architecture, configuration, report=report, unsafe=unsafe) application = Application(architecture, configuration, report=report, unsafe=unsafe)
application.on_start() application.on_start()
packages = Rebuild.extract_packages(application, from_database=args.from_database) packages = Rebuild.extract_packages(application, args.status, from_database=args.from_database)
updates = application.repository.packages_depend_on(packages, args.depends_on or None) updates = application.repository.packages_depend_on(packages, args.depends_on or None)
Rebuild.check_if_empty(args.exit_code, not updates) Rebuild.check_if_empty(args.exit_code, not updates)
@ -60,17 +61,24 @@ class Rebuild(Handler):
Rebuild.check_if_empty(args.exit_code, result.is_empty) Rebuild.check_if_empty(args.exit_code, result.is_empty)
@staticmethod @staticmethod
def extract_packages(application: Application, *, from_database: bool) -> list[Package]: def extract_packages(application: Application, status: BuildStatusEnum | None, *,
from_database: bool) -> list[Package]:
""" """
extract packages from database file extract packages from database file
Args: Args:
application(Application): application instance application(Application): application instance
status(BuildStatusEnum | None): optional filter by package status
from_database(bool): extract packages from database instead of repository filesystem from_database(bool): extract packages from database instead of repository filesystem
Returns: Returns:
list[Package]: list of packages which were stored in database list[Package]: list of packages which were stored in database
""" """
if from_database: if from_database:
return [package for (package, _) in application.database.packages_get()] return [
package
for (package, last_status) in application.database.packages_get()
if status is None or last_status.status == status
]
return application.repository.packages() return application.repository.packages()

View File

@ -40,9 +40,9 @@ class Setup(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
ARCHBUILD_COMMAND_PATH = Path("/usr/bin/archbuild") ARCHBUILD_COMMAND_PATH = Path("/usr") / "bin" / "archbuild"
MIRRORLIST_PATH = Path("/etc/pacman.d/mirrorlist") MIRRORLIST_PATH = Path("/etc") / "pacman.d" / "mirrorlist"
SUDOERS_DIR_PATH = Path("/etc/sudoers.d") SUDOERS_DIR_PATH = Path("/etc") / "sudoers.d"
@classmethod @classmethod
def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
@ -136,7 +136,7 @@ class Setup(Handler):
""" """
create configuration for devtools based on ``source`` configuration create configuration for devtools based on ``source`` configuration
Note: Notes:
devtools does not allow to specify the pacman configuration, thus we still have to use configuration in /usr devtools does not allow to specify the pacman configuration, thus we still have to use configuration in /usr
Args: Args:
@ -178,7 +178,7 @@ class Setup(Handler):
configuration.set_option(repository, "SigLevel", "Optional TrustAll") # we don't care configuration.set_option(repository, "SigLevel", "Optional TrustAll") # we don't care
configuration.set_option(repository, "Server", f"file://{paths.repository}") configuration.set_option(repository, "Server", f"file://{paths.repository}")
target = source.parent / f"pacman-{prefix}-{architecture}.conf" target = source.parent / f"{prefix}-{architecture}.conf"
with target.open("w") as devtools_configuration: with target.open("w") as devtools_configuration:
configuration.write(devtools_configuration) configuration.write(devtools_configuration)

View File

@ -79,5 +79,6 @@ class UnsafeCommands(Handler):
""" """
# should never fail # should never fail
# pylint: disable=protected-access # pylint: disable=protected-access
subparser = next(action for action in parser._actions if isinstance(action, argparse._SubParsersAction)) subparser = next((action for action in parser._actions if isinstance(action, argparse._SubParsersAction)), None)
return sorted(action_name for action_name, action in subparser.choices.items() if action.get_default("unsafe")) actions = subparser.choices if subparser is not None else {}
return sorted(action_name for action_name, action in actions.items() if action.get_default("unsafe"))

View File

@ -78,7 +78,9 @@ class Validate(Handler):
# create trigger loader instance # create trigger loader instance
loader = TriggerLoader() loader = TriggerLoader()
for trigger in loader.selected_triggers(configuration): triggers = loader.selected_triggers(configuration) + loader.known_triggers(configuration)
for trigger in set(triggers):
try: try:
trigger_class = loader.load_trigger_class(trigger) trigger_class = loader.load_trigger_class(trigger)
except ExtensionError: except ExtensionError:

View File

@ -19,6 +19,8 @@
# #
import argparse import argparse
from collections.abc import Generator
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.spawn import Spawn from ahriman.core.spawn import Spawn
@ -31,6 +33,7 @@ class Web(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes
COMMAND_ARGS_WHITELIST = ["force", "log_handler", ""]
@classmethod @classmethod
def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
@ -48,7 +51,8 @@ class Web(Handler):
# we are using local import for optional dependencies # we are using local import for optional dependencies
from ahriman.web.web import run_server, setup_service from ahriman.web.web import run_server, setup_service
spawner = Spawn(args.parser(), architecture, configuration) spawner_args = Web.extract_arguments(args, architecture, configuration)
spawner = Spawn(args.parser(), architecture, list(spawner_args))
spawner.start() spawner.start()
application = setup_service(architecture, configuration, spawner) application = setup_service(architecture, configuration, spawner)
@ -57,3 +61,33 @@ class Web(Handler):
# terminate spawn process at the last # terminate spawn process at the last
spawner.stop() spawner.stop()
spawner.join() spawner.join()
@staticmethod
def extract_arguments(args: argparse.Namespace, architecture: str,
configuration: Configuration) -> Generator[str, None, None]:
"""
extract list of arguments used for current command, except for command specific ones
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
Returns:
Generator[str, None, None]: command line arguments which were used for this specific command
"""
# read architecture from the same argument list
yield from ["--architecture", architecture]
# read configuration path from current settings
if (configuration_path := configuration.path) is not None:
yield from ["--configuration", str(configuration_path)]
# arguments from command line
if args.force:
yield "--force"
if args.log_handler is not None:
yield from ["--log-handler", args.log_handler.value]
if args.quiet:
yield "--quiet"
if args.unsafe:
yield "--unsafe"

View File

@ -20,9 +20,9 @@
import shutil import shutil
from collections.abc import Callable, Generator from collections.abc import Callable, Generator
from functools import cached_property
from pathlib import Path from pathlib import Path
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore[import] from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore[import]
from typing import Any
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
@ -34,13 +34,8 @@ from ahriman.models.repository_paths import RepositoryPaths
class Pacman(LazyLogging): class Pacman(LazyLogging):
""" """
alpm wrapper alpm wrapper
Attributes:
handle(Handle): pyalpm root ``Handle``
""" """
handle: Handle
def __init__(self, architecture: str, configuration: Configuration, *, def __init__(self, architecture: str, configuration: Configuration, *,
refresh_database: PacmanSynchronization) -> None: refresh_database: PacmanSynchronization) -> None:
""" """
@ -84,6 +79,16 @@ class Pacman(LazyLogging):
return handle return handle
@cached_property
def handle(self) -> Handle:
"""
pyalpm handle
Returns:
Handle: generated pyalpm handle instance
"""
return self.__create_handle_fn()
def database_copy(self, handle: Handle, database: DB, pacman_root: Path, paths: RepositoryPaths, *, def database_copy(self, handle: Handle, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
use_ahriman_cache: bool) -> None: use_ahriman_cache: bool) -> None:
""" """
@ -184,22 +189,3 @@ class Pacman(LazyLogging):
result.update(trim_package(provides) for provides in package.provides) result.update(trim_package(provides) for provides in package.provides)
return result return result
def __getattr__(self, item: str) -> Any:
"""
pacman handle extractor
Args:
item(str): property name
Returns:
Any: attribute by its name
Raises:
AttributeError: in case if no such attribute found
"""
if item == "handle":
handle = self.__create_handle_fn()
setattr(self, item, handle)
return handle
return super().__getattr__(item) # required for logging attribute

View File

@ -34,13 +34,15 @@ class Official(Remote):
Attributes: Attributes:
DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url
DEFAULT_ARCHLINUX_GIT_URL(str): (class attribute) default url for git packages
DEFAULT_SEARCH_REPOSITORIES(list[str]): (class attribute) default list of repositories to search 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_RPC_URL(str): (class attribute) default archlinux repositories RPC url
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
""" """
DEFAULT_ARCHLINUX_GIT_URL = "https://gitlab.archlinux.org"
DEFAULT_ARCHLINUX_URL = "https://archlinux.org" DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib", "Community"] DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib"]
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json" DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
DEFAULT_TIMEOUT = 30 DEFAULT_TIMEOUT = 30
@ -56,9 +58,7 @@ class Official(Remote):
Returns: Returns:
str: git url for the specific base str: git url for the specific base
""" """
if repository.lower() in ("core", "extra", "testing", "kde-unstable"): return f"{Official.DEFAULT_ARCHLINUX_GIT_URL}/archlinux/packaging/packages/{package_base}.git"
return "https://github.com/archlinux/svntogit-packages.git" # hardcoded, ok
return "https://github.com/archlinux/svntogit-community.git"
@classmethod @classmethod
def remote_web_url(cls, package_base: str) -> str: def remote_web_url(cls, package_base: str) -> str:

View File

@ -125,6 +125,12 @@ class Sources(LazyLogging):
Sources._check_output("git", "init", "--initial-branch", instance.DEFAULT_BRANCH, Sources._check_output("git", "init", "--initial-branch", instance.DEFAULT_BRANCH,
cwd=sources_dir, logger=instance.logger) cwd=sources_dir, logger=instance.logger)
# extract local files...
files = ["PKGBUILD", ".SRCINFO"] + [str(path) for path in Package.local_files(sources_dir)]
instance.add(sources_dir, *files)
# ...and commit them
instance.commit(sources_dir, author="ahriman <ahriman@localhost>")
@staticmethod @staticmethod
def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> None: def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> None:
""" """

View File

@ -99,6 +99,16 @@ class Configuration(configparser.RawConfigParser):
""" """
return self.getpath("settings", "logging") return self.getpath("settings", "logging")
@property
def repository_name(self) -> str:
"""
repository name as defined by configuration
Returns:
str: repository name from configuration
"""
return self.get("repository", "name")
@property @property
def repository_paths(self) -> RepositoryPaths: def repository_paths(self) -> RepositoryPaths:
""" """

View File

@ -163,6 +163,11 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"coerce": "list", "coerce": "list",
"schema": {"type": "string"}, "schema": {"type": "string"},
}, },
"triggers_known": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
"vcs_allowed_age": { "vcs_allowed_age": {
"type": "integer", "type": "integer",
"coerce": "integer", "coerce": "integer",

View File

@ -144,6 +144,24 @@ class Validator(RootValidator):
if constraint and url.scheme not in constraint: if constraint and url.scheme not in constraint:
self._error(field, f"Url {value} scheme must be one of {constraint}") self._error(field, f"Url {value} scheme must be one of {constraint}")
def _validate_path_is_absolute(self, constraint: bool, field: str, value: Path) -> None:
"""
check if path is absolute or not
Args:
constraint(bool): True in case if path must be absolute and False if it must be relative
field(str): field name to be checked
value(Path): value to be checked
Examples:
The rule's arguments are validated against this schema:
{"type": "boolean"}
"""
if constraint and not value.is_absolute():
self._error(field, f"Path {value} must be absolute")
if not constraint and value.is_absolute():
self._error(field, f"Path {value} must be relative")
def _validate_path_exists(self, constraint: bool, field: str, value: Path) -> None: def _validate_path_exists(self, constraint: bool, field: str, value: Path) -> None:
""" """
check if paths exists check if paths exists
@ -159,3 +177,5 @@ class Validator(RootValidator):
""" """
if constraint and not value.exists(): if constraint and not value.exists():
self._error(field, f"Path {value} must exist") self._error(field, f"Path {value} must exist")
if not constraint and value.exists():
self._error(field, f"Path {value} must not exist")

View File

@ -0,0 +1,80 @@
#
# Copyright (c) 2021-2023 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 sqlite3 import Connection
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.configuration import Configuration
from ahriman.core.util import package_like
from ahriman.models.package import Package
from ahriman.models.pacman_synchronization import PacmanSynchronization
__all__ = ["migrate_data", "steps"]
steps = [
"""
alter table packages add column check_depends json
""",
]
def migrate_data(connection: Connection, configuration: Configuration) -> None:
"""
perform data migration
Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
migrate_package_check_depends(connection, configuration)
def migrate_package_check_depends(connection: Connection, configuration: Configuration) -> None:
"""
migrate package check depends fields
Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
if not configuration.repository_paths.repository.is_dir():
return
_, architecture = configuration.check_loaded()
pacman = Pacman(architecture, configuration, refresh_database=PacmanSynchronization.Disabled)
package_list = []
for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()):
base = Package.from_archive(full_path, pacman, remote=None)
for package, description in base.packages.items():
package_list.append({
"check_depends": description.check_depends,
"package": package,
})
connection.executemany(
"""
update packages set
check_depends = :check_depends
where package = :package
""",
package_list
)

View File

@ -113,17 +113,17 @@ class PackageOperations(Operations):
(package, package_base, architecture, archive_size, (package, package_base, architecture, archive_size,
build_date, depends, description, filename, build_date, depends, description, filename,
"groups", installed_size, licenses, provides, "groups", installed_size, licenses, provides,
url, make_depends, opt_depends) url, make_depends, opt_depends, check_depends)
values values
(:package, :package_base, :architecture, :archive_size, (:package, :package_base, :architecture, :archive_size,
:build_date, :depends, :description, :filename, :build_date, :depends, :description, :filename,
:groups, :installed_size, :licenses, :provides, :groups, :installed_size, :licenses, :provides,
:url, :make_depends, :opt_depends) :url, :make_depends, :opt_depends, :check_depends)
on conflict (package, architecture) do update set on conflict (package, architecture) do update set
package_base = :package_base, archive_size = :archive_size, package_base = :package_base, archive_size = :archive_size,
build_date = :build_date, depends = :depends, description = :description, filename = :filename, build_date = :build_date, depends = :depends, description = :description, filename = :filename,
"groups" = :groups, installed_size = :installed_size, licenses = :licenses, provides = :provides, "groups" = :groups, installed_size = :installed_size, licenses = :licenses, provides = :provides,
url = :url, make_depends = :make_depends, opt_depends = :opt_depends url = :url, make_depends = :make_depends, opt_depends = :opt_depends, check_depends = :check_depends
""", """,
package_list) package_list)

View File

@ -194,6 +194,18 @@ class PasswordError(ValueError):
ValueError.__init__(self, f"Password error: {details}") ValueError.__init__(self, f"Password error: {details}")
class PkgbuildGeneratorError(RuntimeError):
"""
exception class for support type triggers
"""
def __init__(self) -> None:
"""
default constructor
"""
RuntimeError.__init__(self, "Could not generate package")
class ReportError(RuntimeError): class ReportError(RuntimeError):
""" """
report generation exception report generation exception

View File

@ -0,0 +1,47 @@
#
# Copyright (c) 2021-2023 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 logging import NullHandler
from typing import Any
__all__ = ["JournalHandler"]
class _JournalHandler(NullHandler):
"""
wrapper for unexpected args and kwargs
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""
default constructor
Args:
*args(Any): positional arguments
**kwargs(Any): keyword arguments
"""
NullHandler.__init__(self)
del args, kwargs
try:
from systemd.journal import JournalHandler # type: ignore[import]
except ImportError:
JournalHandler = _JournalHandler

View File

@ -21,18 +21,24 @@ import contextlib
import logging import logging
from collections.abc import Generator from collections.abc import Generator
from functools import cached_property
from typing import Any from typing import Any
class LazyLogging: class LazyLogging:
""" """
wrapper for the logger library inspired by scala lazy logging module wrapper for the logger library inspired by scala lazy logging module
Attributes:
logger(logging.Logger): class logger instance
""" """
logger: logging.Logger @cached_property
def logger(self) -> logging.Logger:
"""
get class logger instance
Returns:
logging.Logger: class logger instance
"""
return logging.getLogger(self.logger_name)
@property @property
def logger_name(self) -> str: def logger_name(self) -> str:
@ -89,22 +95,3 @@ class LazyLogging:
yield yield
finally: finally:
self._package_logger_reset() self._package_logger_reset()
def __getattr__(self, item: str) -> Any:
"""
logger extractor
Args:
item(str): property name
Returns:
Any: attribute by its name
Raises:
AttributeError: in case if no such attribute found
"""
if item == "logger":
logger = logging.getLogger(self.logger_name)
setattr(self, item, logger)
return logger
raise AttributeError(f"'{self.__class__.__qualname__}' object has no attribute '{item}'")

View File

@ -20,9 +20,11 @@
import logging import logging
from logging.config import fileConfig from logging.config import fileConfig
from pathlib import Path
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.log.http_log_handler import HttpLogHandler from ahriman.core.log.http_log_handler import HttpLogHandler
from ahriman.models.log_handler import LogHandler
class Log: class Log:
@ -32,24 +34,65 @@ class Log:
Attributes: Attributes:
DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback) 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) DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
DEFAULT_SYSLOG_DEVICE(Path): (class attribute) default path to syslog device
""" """
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s" DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
DEFAULT_LOG_LEVEL = logging.DEBUG DEFAULT_LOG_LEVEL = logging.DEBUG
DEFAULT_SYSLOG_DEVICE = Path("/dev") / "log"
@staticmethod @staticmethod
def load(configuration: Configuration, *, quiet: bool, report: bool) -> None: def handler(selected: LogHandler | None) -> LogHandler:
"""
try to guess default log handler. In case if ``selected`` is set, it will return specified value with appended
_handler suffix. Otherwise, it will try to import journald handler and returns ``journald_handler`` if library
is available. Otherwise, it will check if there is ``/dev/log`` device and returns ``syslog_handler`` in this
case. And, finally, it will fall back to ``console_handler`` if none were found
Args:
selected(LogHandler | None): user specified handler if any
Returns:
LogHandler: selected log handler
"""
if selected is not None:
return selected
try:
from systemd.journal import JournalHandler # type: ignore[import]
del JournalHandler
return LogHandler.Journald # journald import was found
except ImportError:
if Log.DEFAULT_SYSLOG_DEVICE.exists():
return LogHandler.Syslog
return LogHandler.Console
@staticmethod
def load(configuration: Configuration, handler: LogHandler, *, quiet: bool, report: bool) -> None:
""" """
setup logging settings from configuration setup logging settings from configuration
Args: Args:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
handler(LogHandler): selected default log handler, which will be used if no handlers were set
quiet(bool): force disable any log messages quiet(bool): force disable any log messages
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
""" """
default_handler = f"{handler.value}_handler"
try: try:
path = configuration.logging_path log_configuration = Configuration()
fileConfig(path) log_configuration.read(configuration.logging_path)
# set handlers if they are not set
for section in filter(lambda s: s.startswith("logger_"), log_configuration.sections()):
if "handlers" in log_configuration[section]:
continue
log_configuration.set_option(section, "handlers", default_handler)
# load logging configuration
fileConfig(log_configuration, disable_existing_loggers=True)
logging.debug("using %s logger", default_handler)
except Exception: except Exception:
logging.basicConfig(filename=None, format=Log.DEFAULT_LOG_FORMAT, logging.basicConfig(filename=None, format=Log.DEFAULT_LOG_FORMAT,
level=Log.DEFAULT_LOG_LEVEL) level=Log.DEFAULT_LOG_LEVEL)

View File

@ -57,4 +57,4 @@ class HTML(Report, JinjaTemplate):
result(Result): build result result(Result): build result
""" """
html = self.make_html(Result(success=packages), self.template_path) html = self.make_html(Result(success=packages), self.template_path)
self.report_path.write_text(html) self.report_path.write_text(html, encoding="utf8")

View File

@ -75,7 +75,7 @@ class JinjaTemplate:
# base template vars # base template vars
self.homepage = configuration.get(section, "homepage", fallback=None) self.homepage = configuration.get(section, "homepage", fallback=None)
self.name = configuration.get("repository", "name") self.name = configuration.repository_name
self.sign_targets, self.default_pgp_key = GPG.sign_options(configuration) self.sign_targets, self.default_pgp_key = GPG.sign_options(configuration)

View File

@ -67,7 +67,7 @@ class RepositoryProperties(LazyLogging):
self.configuration = configuration self.configuration = configuration
self.database = database self.database = database
self.name = configuration.get("repository", "name") self.name = configuration.repository_name
self.vcs_allowed_age = configuration.getint("build", "vcs_allowed_age", fallback=0) self.vcs_allowed_age = configuration.getint("build", "vcs_allowed_age", fallback=0)
self.paths: RepositoryPaths = configuration.repository_paths # additional workaround for pycharm typing self.paths: RepositoryPaths = configuration.repository_paths # additional workaround for pycharm typing
@ -79,7 +79,7 @@ class RepositoryProperties(LazyLogging):
self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[]) self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
self.pacman = Pacman(architecture, configuration, refresh_database=refresh_pacman_database) self.pacman = Pacman(architecture, configuration, refresh_database=refresh_pacman_database)
self.sign = GPG(architecture, configuration) self.sign = GPG(configuration)
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args) self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
self.reporter = Client.load(configuration, report=report) self.reporter = Client.load(configuration, report=report)
self.triggers = TriggerLoader.load(architecture, configuration) self.triggers = TriggerLoader.load(architecture, configuration)

View File

@ -19,6 +19,7 @@
# #
import requests import requests
from collections.abc import Generator
from pathlib import Path from pathlib import Path
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -34,7 +35,6 @@ class GPG(LazyLogging):
Attributes: Attributes:
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
default_key(str | None): default PGP key ID to use default_key(str | None): default PGP key ID to use
targets(set[SignSettings]): list of targets to sign (repository, package etc) targets(set[SignSettings]): list of targets to sign (repository, package etc)
@ -43,15 +43,13 @@ class GPG(LazyLogging):
_check_output = check_output _check_output = check_output
DEFAULT_TIMEOUT = 30 DEFAULT_TIMEOUT = 30
def __init__(self, architecture: str, configuration: Configuration) -> None: def __init__(self, configuration: Configuration) -> None:
""" """
default constructor default constructor
Args: Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
""" """
self.architecture = architecture
self.configuration = configuration self.configuration = configuration
self.targets, self.default_key = self.sign_options(configuration) self.targets, self.default_key = self.sign_options(configuration)
@ -128,6 +126,34 @@ class GPG(LazyLogging):
raise raise
return response.text return response.text
def key_export(self, key: str) -> str:
"""
export public key from stored keychain
Args:
key(str): key ID to export
Returns:
str: PGP key in .asc format
"""
return GPG._check_output("gpg", "--armor", "--no-emit-version", "--export", key, logger=self.logger)
def key_fingerprint(self, key: str) -> str:
"""
get full key fingerprint from short key id
Args:
key(str): key ID to lookup
Returns:
str: full PGP key fingerprint
"""
metadata = GPG._check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger)
# fingerprint line will be like
# fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines()))
return fingerprint.split(":")[-2]
def key_import(self, server: str, key: str) -> None: def key_import(self, server: str, key: str) -> None:
""" """
import key to current user and sign it locally import key to current user and sign it locally
@ -139,6 +165,21 @@ class GPG(LazyLogging):
key_body = self.key_download(server, key) key_body = self.key_download(server, key)
GPG._check_output("gpg", "--import", input_data=key_body, logger=self.logger) GPG._check_output("gpg", "--import", input_data=key_body, logger=self.logger)
def keys(self) -> list[str]:
"""
extract list of keys described in configuration
Returns:
list[str]: list of unique keys which are set in configuration
"""
def generator() -> Generator[str, None, None]:
if self.default_key is not None:
yield self.default_key
for _, value in filter(lambda pair: pair[0].startswith("key_"), self.configuration["sign"].items()):
yield value
return sorted(set(generator()))
def process(self, path: Path, key: str) -> list[Path]: def process(self, path: Path, key: str) -> list[Path]:
""" """
gpg command wrapper gpg command wrapper
@ -179,7 +220,7 @@ class GPG(LazyLogging):
""" """
sign repository if required by configuration sign repository if required by configuration
Note: Notes:
More likely you just want to pass ``repository_sign_args`` to repo wrapper More likely you just want to pass ``repository_sign_args`` to repo wrapper
Args: Args:

View File

@ -26,7 +26,6 @@ from collections.abc import Callable, Iterable
from multiprocessing import Process, Queue from multiprocessing import Process, Queue
from threading import Lock, Thread from threading import Lock, Thread
from ahriman.core.configuration import Configuration
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
@ -39,23 +38,24 @@ class Spawn(Thread, LazyLogging):
Attributes: Attributes:
active(dict[str, Process]): map of active child processes required to avoid zombies active(dict[str, Process]): map of active child processes required to avoid zombies
architecture(str): repository architecture architecture(str): repository architecture
configuration(Configuration): configuration instance command_arguments(list[str]): base command line arguments
queue(Queue[tuple[str, bool]]): multiprocessing queue to read updates from processes queue(Queue[tuple[str, bool]]): multiprocessing queue to read updates from processes
""" """
def __init__(self, args_parser: argparse.ArgumentParser, architecture: str, configuration: Configuration) -> None: def __init__(self, args_parser: argparse.ArgumentParser, architecture: str, command_arguments: list[str]) -> None:
""" """
default constructor default constructor
Args: Args:
args_parser(argparse.ArgumentParser): command line parser for the application args_parser(argparse.ArgumentParser): command line parser for the application
architecture(str): repository architecture architecture(str): repository architecture
configuration(Configuration): configuration instance command_arguments(list[str]): base command line arguments
""" """
Thread.__init__(self, name="spawn") Thread.__init__(self, name="spawn")
self.architecture = architecture self.architecture = architecture
self.args_parser = args_parser self.args_parser = args_parser
self.configuration = configuration self.command_arguments = command_arguments
self.lock = Lock() self.lock = Lock()
self.active: dict[str, Process] = {} self.active: dict[str, Process] = {}
@ -88,9 +88,7 @@ class Spawn(Thread, LazyLogging):
**kwargs(str): named command arguments **kwargs(str): named command arguments
""" """
# default arguments # default arguments
arguments = ["--architecture", self.architecture] arguments = self.command_arguments[:]
if self.configuration.path is not None:
arguments.extend(["--configuration", str(self.configuration.path)])
# positional command arguments # positional command arguments
arguments.append(command) arguments.append(command)
arguments.extend(args) arguments.extend(args)
@ -172,7 +170,6 @@ class Spawn(Thread, LazyLogging):
process = self.active.pop(process_id, None) process = self.active.pop(process_id, None)
if process is not None: if process is not None:
process.terminate() # make sure lol
process.join() process.join()
def stop(self) -> None: def stop(self) -> None:

View File

@ -102,15 +102,22 @@ class WebClient(Client, LazyLogging):
return address, False return address, False
@contextlib.contextmanager @contextlib.contextmanager
def __get_session(self) -> Generator[requests.Session, None, None]: def __get_session(self, session: requests.Session | None = None) -> Generator[requests.Session, None, None]:
""" """
execute request and handle exceptions execute request and handle exceptions
Args:
session(requests.Session | None, optional): session to be used or stored instance property otherwise
(Default value = None)
Yields: Yields:
requests.Session: session for requests requests.Session: session for requests
""" """
try: try:
yield self.__session if session is not None:
yield session # use session from arguments
else:
yield self.__session # use instance generated session
except requests.RequestException as e: except requests.RequestException as e:
if self.suppress_errors: if self.suppress_errors:
return return
@ -136,13 +143,16 @@ class WebClient(Client, LazyLogging):
return session return session
session = requests.Session() session = requests.Session()
self._login() self._login(session)
return session return session
def _login(self) -> None: def _login(self, session: requests.Session) -> None:
""" """
process login to the service process login to the service
Args:
session(requests.Session): request session to login
""" """
if self.user is None: if self.user is None:
return # no auth configured return # no auth configured
@ -152,7 +162,7 @@ class WebClient(Client, LazyLogging):
"password": self.user.password "password": self.user.password
} }
with self.__get_session() as session: with self.__get_session(session):
response = session.post(self._login_url, json=payload) response = session.post(self._login_url, json=payload)
response.raise_for_status() response.raise_for_status()

View File

@ -0,0 +1,21 @@
#
# Copyright (c) 2021-2023 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.support.keyring_trigger import KeyringTrigger
from ahriman.core.support.mirrorlist_trigger import MirrorlistTrigger

View File

@ -0,0 +1,114 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core import context
from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG
from ahriman.core.support.package_creator import PackageCreator
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
from ahriman.core.triggers import Trigger
from ahriman.models.context_key import ContextKey
class KeyringTrigger(Trigger):
"""
keyring generator trigger
Attributes:
targets(list[str]): git remote target list
"""
CONFIGURATION_SCHEMA = {
"keyring": {
"type": "dict",
"schema": {
"target": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
},
},
"keyring_generator": {
"type": "dict",
"schema": {
"description": {
"type": "string",
},
"homepage": {
"type": "string",
},
"license": {
"type": "list",
"coerce": "list",
},
"package": {
"type": "string",
},
"packagers": {
"type": "list",
"coerce": "list",
},
"revoked": {
"type": "list",
"coerce": "list",
},
"trusted": {
"type": "list",
"coerce": "list",
},
},
},
}
def __init__(self, architecture: str, configuration: Configuration) -> None:
"""
default constructor
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
Trigger.__init__(self, architecture, configuration)
self.targets = self.configuration_sections(configuration)
@classmethod
def configuration_sections(cls, configuration: Configuration) -> list[str]:
"""
extract configuration sections from configuration
Args:
configuration(Configuration): configuration instance
Returns:
list[str]: read configuration sections belong to this trigger
"""
return configuration.getlist("keyring", "target", fallback=[])
def on_start(self) -> None:
"""
trigger action which will be called at the start of the application
"""
ctx = context.get()
sign = ctx.get(ContextKey("sign", GPG))
for target in self.targets:
generator = KeyringGenerator(sign, self.configuration, target)
runner = PackageCreator(self.configuration, generator)
runner.run()

View File

@ -0,0 +1,105 @@
#
# Copyright (c) 2021-2023 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.configuration import Configuration
from ahriman.core.support.package_creator import PackageCreator
from ahriman.core.support.pkgbuild.mirrorlist_generator import MirrorlistGenerator
from ahriman.core.triggers import Trigger
class MirrorlistTrigger(Trigger):
"""
mirrorlist generator trigger
Attributes:
targets(list[str]): git remote target list
"""
CONFIGURATION_SCHEMA = {
"mirrorlist": {
"type": "dict",
"schema": {
"target": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
},
},
"mirrorlist_generator": {
"type": "dict",
"schema": {
"description": {
"type": "string",
},
"homepage": {
"type": "string",
},
"license": {
"type": "list",
"coerce": "list",
},
"package": {
"type": "string",
},
"path": {
"type": "string",
"path_is_absolute": True,
},
"servers": {
"type": "list",
"coerce": "list",
"required": True,
},
},
},
}
def __init__(self, architecture: str, configuration: Configuration) -> None:
"""
default constructor
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
Trigger.__init__(self, architecture, configuration)
self.targets = self.configuration_sections(configuration)
@classmethod
def configuration_sections(cls, configuration: Configuration) -> list[str]:
"""
extract configuration sections from configuration
Args:
configuration(Configuration): configuration instance
Returns:
list[str]: read configuration sections belong to this trigger
"""
return configuration.getlist("mirrorlist", "target", fallback=[])
def on_start(self) -> None:
"""
trigger action which will be called at the start of the application
"""
for target in self.targets:
generator = MirrorlistGenerator(self.configuration, target)
runner = PackageCreator(self.configuration, generator)
runner.run()

View File

@ -0,0 +1,71 @@
#
# Copyright (c) 2021-2023 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 shutil
from ahriman.core import context
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
from ahriman.models.build_status import BuildStatus
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
class PackageCreator:
"""
helper which creates packages based on pkgbuild generator
Attributes:
configuration(Configuration): configuration instance
generator(PkgbuildGenerator): PKGBUILD generator instance
"""
def __init__(self, configuration: Configuration, generator: PkgbuildGenerator) -> None:
"""
default constructor
Args:
configuration(Configuration): configuration instance
generator(PkgbuildGenerator): PKGBUILD generator instance
"""
self.configuration = configuration
self.generator = generator
def run(self) -> None:
"""
create new local package
"""
local_path = self.configuration.repository_paths.cache_for(self.generator.pkgname)
# clear old tree if any
shutil.rmtree(local_path, ignore_errors=True)
# create local tree
local_path.mkdir(mode=0o755, parents=True, exist_ok=True)
self.generator.write_pkgbuild(local_path)
Sources.init(local_path)
# register package
ctx = context.get()
database: SQLite = ctx.get(ContextKey("database", SQLite))
_, architecture = self.configuration.check_loaded()
package = Package.from_build(local_path, architecture)
database.package_update(package, BuildStatus())

View File

@ -0,0 +1,19 @@
#
# Copyright (c) 2021-2023 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/>.
#

View File

@ -0,0 +1,194 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from collections.abc import Callable
from pathlib import Path
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import PkgbuildGeneratorError
from ahriman.core.sign.gpg import GPG
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
class KeyringGenerator(PkgbuildGenerator):
"""
generator for keyring PKGBUILD
Attributes:
sign(GPG): GPG wrapper instance
name(str): repository name
packagers(list[str]): list of packagers PGP keys
pkgbuild_license(list[str]): keyring package license
pkgbuild_pkgdesc(str): keyring package description
pkgbuild_pkgname(str): keyring package name
pkgbuild_url(str): keyring package home page
revoked(list[str]): list of revoked PGP keys
trusted(list[str]): lif of trusted PGP keys
"""
def __init__(self, sign: GPG, configuration: Configuration, section: str) -> None:
"""
default constructor
Args:
sign(GPG): GPG wrapper instance
configuration(Configuration): configuration instance
section(str): settings section name
"""
self.sign = sign
self.name = configuration.repository_name
# configuration fields
self.packagers = configuration.getlist(section, "packagers", fallback=sign.keys())
self.revoked = configuration.getlist(section, "revoked", fallback=[])
self.trusted = configuration.getlist(
section, "trusted", fallback=[sign.default_key] if sign.default_key is not None else [])
# pkgbuild description fields
self.pkgbuild_pkgname = configuration.get(section, "package", fallback=f"{self.name}-keyring")
self.pkgbuild_pkgdesc = configuration.get(section, "description", fallback=f"{self.name} PGP keyring")
self.pkgbuild_license = configuration.getlist(section, "license", fallback=["Unlicense"])
self.pkgbuild_url = configuration.get(section, "homepage", fallback="")
@property
def license(self) -> list[str]:
"""
package licenses list
Returns:
list[str]: package licenses as PKGBUILD property
"""
return self.pkgbuild_license
@property
def pkgdesc(self) -> str:
"""
package description
Returns:
str: package description as PKGBUILD property
"""
return self.pkgbuild_pkgdesc
@property
def pkgname(self) -> str:
"""
package name
Returns:
str: package name as PKGBUILD property
"""
return self.pkgbuild_pkgname
@property
def url(self) -> str:
"""
package upstream url
Returns:
str: package upstream url as PKGBUILD property
"""
return self.pkgbuild_url
def _generate_gpg(self, source_path: Path) -> None:
"""
generate GPG keychain
Args:
source_path(Path): destination of the file content
"""
with source_path.open("w") as source_file:
for key in sorted(set(self.trusted + self.packagers + self.revoked)):
public_key = self.sign.key_export(key)
source_file.write(public_key)
source_file.write("\n")
def _generate_revoked(self, source_path: Path) -> None:
"""
generate revoked PGP keys
Args:
source_path(Path): destination of the file content
"""
with source_path.open("w") as source_file:
for key in sorted(set(self.revoked)):
fingerprint = self.sign.key_fingerprint(key)
source_file.write(fingerprint)
source_file.write("\n")
def _generate_trusted(self, source_path: Path) -> None:
"""
generate trusted PGP keys
Args:
source_path(Path): destination of the file content
"""
if not self.trusted:
raise PkgbuildGeneratorError
with source_path.open("w") as source_file:
for key in sorted(set(self.trusted)):
fingerprint = self.sign.key_fingerprint(key)
source_file.write(fingerprint)
source_file.write(":4:\n")
def install(self) -> str | None:
"""
content of the install functions
Returns:
str | None: content of the install functions if any
"""
# copy-paste from archlinux-keyring
return f"""post_upgrade() {{
if usr/bin/pacman-key -l >/dev/null 2>&1; then
usr/bin/pacman-key --populate {self.name}
usr/bin/pacman-key --updatedb
fi
}}
post_install() {{
if [ -x usr/bin/pacman-key ]; then
post_upgrade
fi
}}"""
def package(self) -> str:
"""
package function generator
Returns:
str: package() function for PKGBUILD
"""
return f"""{{
install -Dm644 "{Path("$srcdir") / f"{self.name}.gpg"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}.gpg"}"
install -Dm644 "{Path("$srcdir") / f"{self.name}-revoked"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-revoked"}"
install -Dm644 "{Path("$srcdir") / f"{self.name}-trusted"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-trusted"}"
}}"""
def sources(self) -> dict[str, Callable[[Path], None]]:
"""
return list of sources for the package
Returns:
dict[str, Callable[[Path], None]]: map of source identifier (e.g. filename) to its generator function
"""
return {
f"{self.name}.gpg": self._generate_gpg,
f"{self.name}-revoked": self._generate_revoked,
f"{self.name}-trusted": self._generate_trusted,
}

View File

@ -0,0 +1,143 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from collections.abc import Callable
from pathlib import Path
from ahriman.core.configuration import Configuration
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
from ahriman.models.pkgbuild_patch import PkgbuildPatch
class MirrorlistGenerator(PkgbuildGenerator):
"""
generator for mirrorlist PKGBUILD
Attributes:
path(Path): path to mirrorlist relative to /
pkgbuild_license(list[str]): mirrorlist package license
pkgbuild_pkgdesc(str): mirrorlist package description
pkgbuild_pkgname(str): mirrorlist package name
pkgbuild_url(str): mirrorlist package home page
servers(list[str]): list of mirror servers
"""
def __init__(self, configuration: Configuration, section: str) -> None:
"""
default constructor
Args:
configuration(Configuration): configuration instance
section(str): settings section name
"""
name = configuration.repository_name
# configuration fields
self.servers = configuration.getlist(section, "servers")
self.path = configuration.getpath(section, "path", fallback=Path("/etc") / "pacman.d" / f"{name}-mirrorlist")
self.path = self.path.relative_to("/") # in pkgbuild we are always operating with relative to / path
# pkgbuild description fields
self.pkgbuild_pkgname = configuration.get(section, "package", fallback=f"{name}-mirrorlist")
self.pkgbuild_pkgdesc = configuration.get(
section, "description", fallback=f"{name} mirror list for use by pacman")
self.pkgbuild_license = configuration.getlist(section, "license", fallback=["Unlicense"])
self.pkgbuild_url = configuration.get(section, "homepage", fallback="")
@property
def license(self) -> list[str]:
"""
package licenses list
Returns:
list[str]: package licenses as PKGBUILD property
"""
return self.pkgbuild_license
@property
def pkgdesc(self) -> str:
"""
package description
Returns:
str: package description as PKGBUILD property
"""
return self.pkgbuild_pkgdesc
@property
def pkgname(self) -> str:
"""
package name
Returns:
str: package name as PKGBUILD property
"""
return self.pkgbuild_pkgname
@property
def url(self) -> str:
"""
package upstream url
Returns:
str: package upstream url as PKGBUILD property
"""
return self.pkgbuild_url
def _generate_mirrorlist(self, source_path: Path) -> None:
"""
generate mirrorlist file
Args:
source_path(Path): destination of the mirrorlist content
"""
content = "".join([f"Server = {server}\n" for server in self.servers])
source_path.write_text(content, encoding="utf8")
def package(self) -> str:
"""
package function generator
Returns:
str: package() function for PKGBUILD
"""
return f"""{{
install -Dm644 "{Path("$srcdir") / "mirrorlist"}" "{Path("$pkgdir") / self.path}"
}}"""
def patches(self) -> list[PkgbuildPatch]:
"""
list of additional PKGBUILD properties
Returns:
list[PkgbuildPatch]: list of patches which generate PKGBUILD content
"""
return [
PkgbuildPatch("backup", [str(self.path)]),
]
def sources(self) -> dict[str, Callable[[Path], None]]:
"""
return list of sources for the package
Returns:
dict[str, Callable[[Path], None]]: map of source identifier (e.g. filename) to its generator function
"""
return {
"mirrorlist": self._generate_mirrorlist,
}

View File

@ -0,0 +1,201 @@
#
# Copyright (c) 2021-2023 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 hashlib
import itertools
from collections.abc import Callable, Generator
from pathlib import Path
from ahriman.core.util import utcnow
from ahriman.models.pkgbuild_patch import PkgbuildPatch
class PkgbuildGenerator:
"""
main class for generating PKGBUILDs
Attributes:
PKGBUILD_STATIC_PROPERTIES(list[PkgbuildPatch]): (class attribute) list of default pkgbuild static properties
"""
PKGBUILD_STATIC_PROPERTIES = [
PkgbuildPatch("pkgrel", "1"),
PkgbuildPatch("arch", ["any"]),
]
@property
def license(self) -> list[str]:
"""
package licenses list
Returns:
list[str]: package licenses as PKGBUILD property
"""
return []
@property
def pkgdesc(self) -> str:
"""
package description
Returns:
str: package description as PKGBUILD property
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
@property
def pkgname(self) -> str:
"""
package name
Returns:
str: package name as PKGBUILD property
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
@property
def pkgver(self) -> str:
"""
package version
Returns:
str: package version as PKGBUILD property
"""
return utcnow().strftime("%Y%m%d")
@property
def url(self) -> str:
"""
package upstream url
Returns:
str: package upstream url as PKGBUILD property
"""
return ""
def install(self) -> str | None:
"""
content of the install functions
Returns:
str | None: content of the install functions if any
"""
def package(self) -> str:
"""
package function generator
Returns:
str: package() function for PKGBUILD
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def patches(self) -> list[PkgbuildPatch]:
"""
list of additional PKGBUILD properties
Returns:
list[PkgbuildPatch]: list of patches which generate PKGBUILD content
"""
return []
def sources(self) -> dict[str, Callable[[Path], None]]:
"""
return list of sources for the package
Returns:
dict[str, Callable[[Path], None]]: map of source identifier (e.g. filename) to its generator function
"""
return {}
def write_install(self, source_dir: Path) -> list[PkgbuildPatch]:
"""
generate content of install file
Args:
source_dir(Path): path to directory in which sources must be generated
Returns:
list[PkgbuildPatch]: patch for the pkgbuild if install file exists and empty list otherwise
"""
content: str | None = self.install()
if content is None:
return []
source_path = source_dir / f"{self.pkgname}.install"
source_path.write_text(content)
return [PkgbuildPatch("install", source_path.name)]
def write_pkgbuild(self, source_dir: Path) -> None:
"""
generate PKGBUILD content to the specified path
Args:
source_dir(Path): path to directory in which sources must be generated
"""
patches = self.PKGBUILD_STATIC_PROPERTIES # default static properties...
patches.extend([
PkgbuildPatch("license", self.license),
PkgbuildPatch("pkgdesc", self.pkgdesc),
PkgbuildPatch("pkgname", self.pkgname),
PkgbuildPatch("pkgver", self.pkgver),
PkgbuildPatch("url", self.url),
]) # ...main properties as defined by derived class...
patches.extend(self.patches()) # ...optional properties as defined by derived class...
patches.extend(self.write_install(source_dir)) # ...install function...
patches.append(PkgbuildPatch("package()", self.package())) # ...package function...
patches.extend(self.write_sources(source_dir)) # ...and finally source files
for patch in patches:
patch.write(source_dir / "PKGBUILD")
def write_sources(self, source_dir: Path) -> list[PkgbuildPatch]:
"""
write sources and returns valid PKGBUILD properties for them
Args:
source_dir(Path): path to directory in which sources must be generated
Returns:
list[PkgbuildPatch]: list of patches to be applied to the PKGBUILD
"""
def sources_generator() -> Generator[tuple[str, str], None, None]:
for source, generator in sorted(self.sources().items()):
source_path = source_dir / source
generator(source_path)
with source_path.open("rb") as source_file:
source_hash = hashlib.sha512(source_file.read())
yield source, source_hash.hexdigest()
sources_iter, hashes_iter = itertools.tee(sources_generator())
return [
PkgbuildPatch("source", [source for source, _ in sources_iter]),
PkgbuildPatch("sha512sums", [sha512 for _, sha512 in hashes_iter]),
]

View File

@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Callable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.configuration.schema import ConfigurationSchema from ahriman.core.configuration.schema import ConfigurationSchema
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
@ -128,8 +130,10 @@ class Trigger(LazyLogging):
result(Result): build result result(Result): build result
packages(list[Package]): list of all available packages packages(list[Package]): list of all available packages
""" """
if (run := getattr(self, "run", None)) is not None: # compatibility with old triggers
run(result, packages) # compatibility with old triggers run: Callable[[Result, list[Package]], None] | None = getattr(self, "run", None)
if run is not None:
run(result, packages)
def on_start(self) -> None: def on_start(self) -> None:
""" """

View File

@ -84,6 +84,20 @@ class TriggerLoader(LazyLogging):
return instance return instance
@staticmethod
def known_triggers(configuration: Configuration) -> list[str]:
"""
read configuration and return list of known triggers. Unlike ``selected_triggers`` this option is used mainly
for configuration and validation and mentioned triggers are not being executed automatically
Args:
configuration(Configuration): configuration instance
Returns:
list[str]: list of registered, but not enabled, triggers
"""
return configuration.getlist("build", "triggers_known", fallback=[])
@staticmethod @staticmethod
def selected_triggers(configuration: Configuration) -> list[str]: def selected_triggers(configuration: Configuration) -> list[str]:
""" """

View File

@ -17,6 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# pylint: disable=too-many-lines
import datetime import datetime
import io import io
import itertools import itertools
@ -48,6 +49,8 @@ __all__ = [
"pretty_datetime", "pretty_datetime",
"pretty_size", "pretty_size",
"safe_filename", "safe_filename",
"srcinfo_property",
"srcinfo_property_list",
"trim_package", "trim_package",
"utcnow", "utcnow",
"walk", "walk",
@ -117,8 +120,6 @@ def check_output(*args: str, exception: Exception | None = None, cwd: Path | Non
result: list[str] = [] result: list[str] = []
for line in iter(get_io(process, "stdout").readline, ""): for line in iter(get_io(process, "stdout").readline, ""):
line = line.strip() line = line.strip()
if not line: # skip empty lines
continue
result.append(line) result.append(line)
log(line) log(line)
@ -133,7 +134,7 @@ def check_output(*args: str, exception: Exception | None = None, cwd: Path | Non
raise exception raise exception
raise subprocess.CalledProcessError(status_code, process.args) raise subprocess.CalledProcessError(status_code, process.args)
return "\n".join(result) return "\n".join(result).rstrip("\n") # remove newline at the end of any
def check_user(paths: RepositoryPaths, *, unsafe: bool) -> None: def check_user(paths: RepositoryPaths, *, unsafe: bool) -> None:
@ -328,6 +329,47 @@ def safe_filename(source: str) -> str:
return re.sub(r"[^A-Za-z\d\-._~:\[\]@]", "-", source) return re.sub(r"[^A-Za-z\d\-._~:\[\]@]", "-", source)
def srcinfo_property(key: str, srcinfo: dict[str, Any], package_srcinfo: dict[str, Any], *,
default: Any = None) -> Any:
"""
extract property from SRCINFO. This method extracts property from package if this property is presented in
``package``. Otherwise, it looks for the same property in root srcinfo. If none found, the default value will be
returned
Args:
key(str): key to extract from srcinfo
srcinfo(dict[str, Any]): root structure of SRCINFO
package_srcinfo(dict[str, Any]): package specific SRCINFO
default(Any, optional): the default value for the specified key (Default value = None)
Returns:
Any: extracted value from SRCINFO
"""
return package_srcinfo.get(key) or srcinfo.get(key) or default
def srcinfo_property_list(key: str, srcinfo: dict[str, Any], package_srcinfo: dict[str, Any], *,
architecture: str | None = None) -> list[Any]:
"""
extract list property from SRCINFO. Unlike ``srcinfo_property`` it supposes that default return value is always
empty list. If ``architecture`` is supplied, then it will try to lookup for architecture specific values and will
append it at the end of result
Args:
key(str): key to extract from srcinfo
srcinfo(dict[str, Any]): root structure of SRCINFO
package_srcinfo(dict[str, Any]): package specific SRCINFO
architecture(str | None, optional): package architecture if set (Default value = None)
Returns:
list[Any]: list of extracted properties from SRCINFO
"""
values: list[Any] = srcinfo_property(key, srcinfo, package_srcinfo, default=[])
if architecture is not None:
values.extend(srcinfo_property(f"{key}_{architecture}", srcinfo, package_srcinfo, default=[]))
return values
def trim_package(package_name: str) -> str: def trim_package(package_name: str) -> str:
""" """
remove version bound and description from package name. Pacman allows to specify version bound (=, <=, >= etc) for remove version bound and description from package name. Pacman allows to specify version bound (=, <=, >= etc) for

View File

@ -52,6 +52,7 @@ class AURPackage:
depends(list[str]): list of package dependencies depends(list[str]): list of package dependencies
make_depends(l[str]): list of package make dependencies make_depends(l[str]): list of package make dependencies
opt_depends(list[str]): list of package optional dependencies opt_depends(list[str]): list of package optional dependencies
check_depends(list[str]): list of package test dependencies
conflicts(list[str]): conflicts list for the package conflicts(list[str]): conflicts list for the package
provides(list[str]): list of packages which this package provides provides(list[str]): list of packages which this package provides
license(list[str]): list of package licenses license(list[str]): list of package licenses
@ -94,6 +95,7 @@ class AURPackage:
depends: list[str] = field(default_factory=list) depends: list[str] = field(default_factory=list)
make_depends: list[str] = field(default_factory=list) make_depends: list[str] = field(default_factory=list)
opt_depends: list[str] = field(default_factory=list) opt_depends: list[str] = field(default_factory=list)
check_depends: list[str] = field(default_factory=list)
conflicts: list[str] = field(default_factory=list) conflicts: list[str] = field(default_factory=list)
provides: list[str] = field(default_factory=list) provides: list[str] = field(default_factory=list)
license: list[str] = field(default_factory=list) license: list[str] = field(default_factory=list)
@ -146,6 +148,7 @@ class AURPackage:
depends=package.depends, depends=package.depends,
make_depends=package.makedepends, make_depends=package.makedepends,
opt_depends=package.optdepends, opt_depends=package.optdepends,
check_depends=package.checkdepends,
conflicts=package.conflicts, conflicts=package.conflicts,
provides=package.provides, provides=package.provides,
license=package.licenses, license=package.licenses,
@ -185,6 +188,7 @@ class AURPackage:
depends=dump["depends"], depends=dump["depends"],
make_depends=dump["makedepends"], make_depends=dump["makedepends"],
opt_depends=dump["optdepends"], opt_depends=dump["optdepends"],
check_depends=dump["checkdepends"],
conflicts=dump["conflicts"], conflicts=dump["conflicts"],
provides=dump["provides"], provides=dump["provides"],
license=dump["licenses"], license=dump["licenses"],

View File

@ -0,0 +1,35 @@
#
# Copyright (c) 2021-2023 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 enum import Enum
class LogHandler(str, Enum):
"""
log handler as described by default configuration
Attributes:
Console(LogHandler): (class attribute) write logs to console
Syslog(LogHandler): (class attribute) write logs to syslog device /dev/null
Journald(LogHandler): (class attribute) write logs to journald directly
"""
Console = "console"
Syslog = "syslog"
Journald = "journald"

View File

@ -22,18 +22,19 @@ from __future__ import annotations
import copy import copy
from collections.abc import Iterable from collections.abc import Callable, Generator, Iterable
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from pathlib import Path from pathlib import Path
from pyalpm import vercmp # type: ignore[import] from pyalpm import vercmp # type: ignore[import]
from srcinfo.parse import parse_srcinfo # type: ignore[import] from srcinfo.parse import parse_srcinfo # type: ignore[import]
from typing import Any, Self from typing import Any, Self
from urllib.parse import urlparse
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb
from ahriman.core.exceptions import PackageInfoError from ahriman.core.exceptions import PackageInfoError
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.util import check_output, full_version, utcnow from ahriman.core.util import check_output, full_version, srcinfo_property_list, utcnow
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource from ahriman.models.remote_source import RemoteSource
@ -87,7 +88,7 @@ class Package(LazyLogging):
Returns: Returns:
list[str]: sum of dependencies per each package list[str]: sum of dependencies per each package
""" """
return sorted(set(sum((package.depends for package in self.packages.values()), start=[]))) return self._package_list_property(lambda package: package.depends)
@property @property
def depends_build(self) -> set[str]: def depends_build(self) -> set[str]:
@ -97,7 +98,17 @@ class Package(LazyLogging):
Returns: Returns:
set[str]: full dependencies list used by devtools set[str]: full dependencies list used by devtools
""" """
return (set(self.depends) | set(self.depends_make)).difference(self.packages_full) return (set(self.depends) | set(self.depends_make) | set(self.depends_check)).difference(self.packages_full)
@property
def depends_check(self) -> list[str]:
"""
get package test dependencies
Returns:
list[str]: sum of test dependencies per each package
"""
return self._package_list_property(lambda package: package.check_depends)
@property @property
def depends_make(self) -> list[str]: def depends_make(self) -> list[str]:
@ -107,7 +118,7 @@ class Package(LazyLogging):
Returns: Returns:
list[str]: sum of make dependencies per each package list[str]: sum of make dependencies per each package
""" """
return sorted(set(sum((package.make_depends for package in self.packages.values()), start=[]))) return self._package_list_property(lambda package: package.make_depends)
@property @property
def depends_opt(self) -> list[str]: def depends_opt(self) -> list[str]:
@ -117,7 +128,7 @@ class Package(LazyLogging):
Returns: Returns:
list[str]: sum of optional dependencies per each package list[str]: sum of optional dependencies per each package
""" """
return sorted(set(sum((package.opt_depends for package in self.packages.values()), start=[]))) return self._package_list_property(lambda package: package.opt_depends)
@property @property
def groups(self) -> list[str]: def groups(self) -> list[str]:
@ -127,7 +138,7 @@ class Package(LazyLogging):
Returns: Returns:
list[str]: sum of groups per each package list[str]: sum of groups per each package
""" """
return sorted(set(sum((package.groups for package in self.packages.values()), start=[]))) return self._package_list_property(lambda package: package.groups)
@property @property
def is_single_package(self) -> bool: def is_single_package(self) -> bool:
@ -162,7 +173,7 @@ class Package(LazyLogging):
Returns: Returns:
list[str]: sum of licenses per each package list[str]: sum of licenses per each package
""" """
return sorted(set(sum((package.licenses for package in self.packages.values()), start=[]))) return self._package_list_property(lambda package: package.licenses)
@property @property
def packages_full(self) -> list[str]: def packages_full(self) -> list[str]:
@ -235,23 +246,26 @@ class Package(LazyLogging):
if errors: if errors:
raise PackageInfoError(errors) raise PackageInfoError(errors)
def get_property(key: str, properties: dict[str, Any], default: Any) -> Any:
return properties.get(key) or srcinfo.get(key) or default
def get_list(key: str, properties: dict[str, Any]) -> Any:
return get_property(key, properties, []) + get_property(f"{key}_{architecture}", properties, [])
packages = { packages = {
package: PackageDescription( package: PackageDescription(
depends=get_list("depends", properties), depends=srcinfo_property_list("depends", srcinfo, properties, architecture=architecture),
make_depends=get_list("makedepends", properties), make_depends=srcinfo_property_list("makedepends", srcinfo, properties, architecture=architecture),
opt_depends=get_list("optdepends", properties), opt_depends=srcinfo_property_list("optdepends", srcinfo, properties, architecture=architecture),
check_depends=srcinfo_property_list("checkdepends", srcinfo, properties, architecture=architecture),
) )
for package, properties in srcinfo["packages"].items() for package, properties in srcinfo["packages"].items()
} }
version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"]) version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"])
return cls(base=srcinfo["pkgbase"], version=version, remote=None, packages=packages) remote = RemoteSource(
git_url=path.absolute().as_uri(),
web_url="",
path=".",
branch="master",
source=PackageSource.Local,
)
return cls(base=srcinfo["pkgbase"], version=version, remote=remote, packages=packages)
@classmethod @classmethod
def from_json(cls, dump: dict[str, Any]) -> Self: def from_json(cls, dump: dict[str, Any]) -> Self:
@ -293,6 +307,41 @@ class Package(LazyLogging):
remote=remote, remote=remote,
packages={package.name: PackageDescription.from_aur(package)}) packages={package.name: PackageDescription.from_aur(package)})
@staticmethod
def local_files(path: Path) -> Generator[Path, None, None]:
"""
extract list of local files
Args:
path(Path): path to package sources directory
Returns:
Generator[Path, None, None]: list of paths of files which belong to the package and distributed together
with this tarball. All paths are relative to the ``path``
"""
srcinfo_source = Package._check_output("makepkg", "--printsrcinfo", cwd=path)
srcinfo, errors = parse_srcinfo(srcinfo_source)
if errors:
raise PackageInfoError(errors)
# we could use arch property, but for consistency it is better to call special method
architectures = Package.supported_architectures(path)
for architecture in architectures:
for source in srcinfo_property_list("source", srcinfo, {}, architecture=architecture):
if "::" in source:
_, source = source.split("::", 1) # in case if filename is specified, remove it
if urlparse(source).scheme:
# basically file schema should use absolute path which is impossible if we are distributing
# files together with PKGBUILD. In this case we are going to skip it also
continue
yield Path(source)
if (install := srcinfo.get("install", None)) is not None:
yield Path(install)
@staticmethod @staticmethod
def supported_architectures(path: Path) -> set[str]: def supported_architectures(path: Path) -> set[str]:
""" """
@ -313,6 +362,26 @@ class Package(LazyLogging):
raise PackageInfoError(errors) raise PackageInfoError(errors)
return set(srcinfo.get("arch", [])) return set(srcinfo.get("arch", []))
def _package_list_property(self, extractor: Callable[[PackageDescription], list[str]]) -> list[str]:
"""
extract list property from single packages and combine them into one list
Notes:
Basically this method is generic for type of ``list[T]``, but there is no trait ``Comparable`` in default
packages, thus we limit this method only to new types
Args:
extractor(Callable[[PackageDescription], list[str]): package property extractor
Returns:
list[str]: combined list of unique entries in properties list
"""
def generator() -> Generator[str, None, None]:
for package in self.packages.values():
yield from extractor(package)
return sorted(set(generator()))
def actual_version(self, paths: RepositoryPaths) -> str: def actual_version(self, paths: RepositoryPaths) -> str:
""" """
additional method to handle VCS package versions additional method to handle VCS package versions

View File

@ -35,6 +35,7 @@ class PackageDescription:
architecture(str | None): package architecture architecture(str | None): package architecture
archive_size(int | None): package archive size archive_size(int | None): package archive size
build_date(int | None): package build date build_date(int | None): package build date
check_depends(list[str]): package dependencies list used for check functions
depends(list[str]): package dependencies list depends(list[str]): package dependencies list
opt_depends(list[str]): optional package dependencies list opt_depends(list[str]): optional package dependencies list
make_depends(list[str]): package dependencies list used for building make_depends(list[str]): package dependencies list used for building
@ -70,6 +71,7 @@ class PackageDescription:
depends: list[str] = field(default_factory=list) depends: list[str] = field(default_factory=list)
make_depends: list[str] = field(default_factory=list) make_depends: list[str] = field(default_factory=list)
opt_depends: list[str] = field(default_factory=list) opt_depends: list[str] = field(default_factory=list)
check_depends: list[str] = field(default_factory=list)
description: str | None = None description: str | None = None
filename: str | None = None filename: str | None = None
groups: list[str] = field(default_factory=list) groups: list[str] = field(default_factory=list)
@ -85,6 +87,7 @@ class PackageDescription:
self.depends = [trim_package(package) for package in self.depends] self.depends = [trim_package(package) for package in self.depends]
self.opt_depends = [trim_package(package) for package in self.opt_depends] self.opt_depends = [trim_package(package) for package in self.opt_depends]
self.make_depends = [trim_package(package) for package in self.make_depends] self.make_depends = [trim_package(package) for package in self.make_depends]
self.check_depends = [trim_package(package) for package in self.check_depends]
@property @property
def filepath(self) -> Path | None: def filepath(self) -> Path | None:
@ -111,6 +114,7 @@ class PackageDescription:
depends=package.depends, depends=package.depends,
make_depends=package.make_depends, make_depends=package.make_depends,
opt_depends=package.opt_depends, opt_depends=package.opt_depends,
check_depends=package.check_depends,
description=package.description, description=package.description,
licenses=package.license, licenses=package.license,
provides=package.provides, provides=package.provides,
@ -151,6 +155,7 @@ class PackageDescription:
depends=package.depends, depends=package.depends,
make_depends=package.makedepends, make_depends=package.makedepends,
opt_depends=package.optdepends, opt_depends=package.optdepends,
check_depends=package.checkdepends,
description=package.desc, description=package.desc,
filename=path.name, filename=path.name,
groups=package.groups, groups=package.groups,

View File

@ -105,8 +105,8 @@ class RemoteSource:
return cls( return cls(
git_url=Official.remote_git_url(package_base, repository), git_url=Official.remote_git_url(package_base, repository),
web_url=Official.remote_web_url(package_base), web_url=Official.remote_web_url(package_base),
path="trunk", path=".",
branch=f"packages/{package_base}", branch="main",
source=source, source=source,
) )
return None return None

View File

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

View File

@ -49,6 +49,10 @@ class PackagePropertiesSchema(Schema):
"description": "Package optional dependencies list", "description": "Package optional dependencies list",
"example": ["python-aiohttp"], "example": ["python-aiohttp"],
}) })
check_depends = fields.List(fields.String(), metadata={
"description": "Package test dependencies list",
"example": ["python-pytest"],
})
description = fields.String(metadata={ description = fields.String(metadata={
"description": "Package description", "description": "Package description",
"example": "ArcH linux ReposItory MANager", "example": "ArcH linux ReposItory MANager",

View File

@ -74,7 +74,7 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
package_mock = mocker.patch("ahriman.models.package.Package.from_aur", side_effect=lambda p, _: packages[p]) package_mock = mocker.patch("ahriman.models.package.Package.from_aur", side_effect=lambda p, _: packages[p])
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages", packages_mock = mocker.patch("ahriman.application.application.Application._known_packages",
return_value=["devtools", "python-build"]) return_value=["devtools", "python-build", "python-pytest"])
result = application.with_dependencies([package_ahriman], process_dependencies=True) result = application.with_dependencies([package_ahriman], process_dependencies=True)
assert {package.base: package for package in result} == packages assert {package.base: package for package in result} == packages

View File

@ -5,25 +5,36 @@ from pytest_mock import MockerFixture
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.application.application.application_packages import ApplicationPackages from ahriman.application.application.application_packages import ApplicationPackages
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.result import Result from ahriman.models.result import Result
def test_add_archive( def test_add_archive(application_packages: ApplicationPackages, package_ahriman: Package,
application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
package_ahriman: Package,
mocker: MockerFixture) -> None:
""" """
must add package from archive must add package from archive
""" """
is_file_mock = mocker.patch("pathlib.Path.is_file", return_value=True)
copy_mock = mocker.patch("shutil.copy") copy_mock = mocker.patch("shutil.copy")
application_packages._add_archive(package_ahriman.base) application_packages._add_archive(package_ahriman.base)
is_file_mock.assert_called_once_with()
copy_mock.assert_called_once_with( copy_mock.assert_called_once_with(
Path(package_ahriman.base), application_packages.repository.paths.packages / package_ahriman.base) Path(package_ahriman.base), application_packages.repository.paths.packages / package_ahriman.base)
def test_add_archive_missing(application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
"""
must raise UnknownPackageError on unknown path
"""
mocker.patch("pathlib.Path.is_file", return_value=False)
with pytest.raises(UnknownPackageError):
application_packages._add_archive("package")
def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Package, mocker: MockerFixture) -> None: def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must add package from AUR must add package from AUR
@ -37,21 +48,29 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac
update_remote_mock.assert_called_once_with(package_ahriman) update_remote_mock.assert_called_once_with(package_ahriman)
def test_add_directory( def test_add_directory(application_packages: ApplicationPackages, package_ahriman: Package,
application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
package_ahriman: Package,
mocker: MockerFixture) -> None:
""" """
must add packages from directory must add packages from directory
""" """
iterdir_mock = mocker.patch("pathlib.Path.iterdir", is_dir_mock = mocker.patch("pathlib.Path.is_dir", return_value=True)
return_value=[package.filepath for package in package_ahriman.packages.values()])
copy_mock = mocker.patch("shutil.copy")
filename = package_ahriman.packages[package_ahriman.base].filepath filename = package_ahriman.packages[package_ahriman.base].filepath
iterdir_mock = mocker.patch("pathlib.Path.iterdir", return_value=[filename])
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_archive")
application_packages._add_directory(package_ahriman.base) application_packages._add_directory(package_ahriman.base)
is_dir_mock.assert_called_once_with()
iterdir_mock.assert_called_once_with() iterdir_mock.assert_called_once_with()
copy_mock.assert_called_once_with(filename, application_packages.repository.paths.packages / filename.name) add_mock.assert_called_once_with(str(filename))
def test_add_directory_missing(application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
"""
must raise UnknownPackageError on unknown directory path
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
with pytest.raises(UnknownPackageError):
application_packages._add_directory("package")
def test_add_local(application_packages: ApplicationPackages, package_ahriman: Package, mocker: MockerFixture) -> None: def test_add_local(application_packages: ApplicationPackages, package_ahriman: Package, mocker: MockerFixture) -> None:
@ -59,17 +78,46 @@ def test_add_local(application_packages: ApplicationPackages, package_ahriman: P
must add package from local sources must add package from local sources
""" """
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
is_dir_mock = mocker.patch("pathlib.Path.is_dir", return_value=True)
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init") init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
copytree_mock = mocker.patch("shutil.copytree") copytree_mock = mocker.patch("shutil.copytree")
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert") build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
application_packages._add_local(package_ahriman.base) application_packages._add_local(package_ahriman.base)
is_dir_mock.assert_called_once_with()
copytree_mock.assert_called_once_with( copytree_mock.assert_called_once_with(
Path(package_ahriman.base), application_packages.repository.paths.cache_for(package_ahriman.base)) Path(package_ahriman.base), application_packages.repository.paths.cache_for(package_ahriman.base))
init_mock.assert_called_once_with(application_packages.repository.paths.cache_for(package_ahriman.base)) init_mock.assert_called_once_with(application_packages.repository.paths.cache_for(package_ahriman.base))
build_queue_mock.assert_called_once_with(package_ahriman) build_queue_mock.assert_called_once_with(package_ahriman)
def test_add_local_cache(application_packages: ApplicationPackages, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must add package from local source if there is cache
"""
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
mocker.patch("pathlib.Path.is_dir", autospec=True,
side_effect=lambda p: True if p.is_relative_to(application_packages.repository.paths.cache) else False)
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
copytree_mock = mocker.patch("shutil.copytree")
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
application_packages._add_local(package_ahriman.base)
copytree_mock.assert_not_called()
init_mock.assert_not_called()
build_queue_mock.assert_called_once_with(package_ahriman)
def test_add_local_missing(application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
"""
must raise UnknownPackageError if package wasn't found
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
with pytest.raises(UnknownPackageError):
application_packages._add_local("package")
def test_add_remote(application_packages: ApplicationPackages, package_description_ahriman: PackageDescription, def test_add_remote(application_packages: ApplicationPackages, package_description_ahriman: PackageDescription,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """
@ -87,6 +135,15 @@ def test_add_remote(application_packages: ApplicationPackages, package_descripti
response_mock.raise_for_status.assert_called_once_with() response_mock.raise_for_status.assert_called_once_with()
def test_add_remote_missing(application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
"""
must add package from remote source
"""
mocker.patch("requests.get", side_effect=Exception())
with pytest.raises(UnknownPackageError):
application_packages._add_remote("url")
def test_add_repository(application_packages: ApplicationPackages, package_ahriman: Package, def test_add_repository(application_packages: ApplicationPackages, package_ahriman: Package,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """
@ -112,10 +169,8 @@ def test_add_add_archive(application_packages: ApplicationPackages, package_ahri
add_mock.assert_called_once_with(package_ahriman.base) add_mock.assert_called_once_with(package_ahriman.base)
def test_add_add_aur( def test_add_add_aur(application_packages: ApplicationPackages, package_ahriman: Package,
application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
package_ahriman: Package,
mocker: MockerFixture) -> None:
""" """
must add package from AUR via add function must add package from AUR via add function
""" """

View File

@ -7,6 +7,7 @@ from pytest_mock import MockerFixture
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError
from ahriman.models.log_handler import LogHandler
def test_architectures_extract(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: def test_architectures_extract(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
@ -56,17 +57,20 @@ def test_call(args: argparse.Namespace, configuration: Configuration, mocker: Mo
must call inside lock must call inside lock
""" """
args.configuration = Path("") args.configuration = Path("")
args.log_handler = LogHandler.Console
args.quiet = False args.quiet = False
args.report = False args.report = False
mocker.patch("ahriman.application.handlers.Handler.run") mocker.patch("ahriman.application.handlers.Handler.run")
configuration_mock = mocker.patch("ahriman.core.configuration.Configuration.from_path", return_value=configuration) configuration_mock = mocker.patch("ahriman.core.configuration.Configuration.from_path", return_value=configuration)
log_handler_mock = mocker.patch("ahriman.core.log.Log.handler", return_value=args.log_handler)
log_load_mock = mocker.patch("ahriman.core.log.Log.load") log_load_mock = mocker.patch("ahriman.core.log.Log.load")
enter_mock = mocker.patch("ahriman.application.lock.Lock.__enter__") enter_mock = mocker.patch("ahriman.application.lock.Lock.__enter__")
exit_mock = mocker.patch("ahriman.application.lock.Lock.__exit__") exit_mock = mocker.patch("ahriman.application.lock.Lock.__exit__")
assert Handler.call(args, "x86_64") assert Handler.call(args, "x86_64")
configuration_mock.assert_called_once_with(args.configuration, "x86_64") configuration_mock.assert_called_once_with(args.configuration, "x86_64")
log_load_mock.assert_called_once_with(configuration, quiet=args.quiet, report=args.report) log_handler_mock.assert_called_once_with(args.log_handler)
log_load_mock.assert_called_once_with(configuration, args.log_handler, quiet=args.quiet, report=args.report)
enter_mock.assert_called_once_with() enter_mock.assert_called_once_with()
exit_mock.assert_called_once_with(None, None, None) exit_mock.assert_called_once_with(None, None, None)

View File

@ -8,6 +8,7 @@ from ahriman.application.application import Application
from ahriman.application.handlers import Rebuild from ahriman.application.handlers import Rebuild
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result
@ -26,6 +27,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
args.dry_run = False args.dry_run = False
args.from_database = False args.from_database = False
args.exit_code = False args.exit_code = False
args.status = None
return args return args
@ -46,7 +48,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start") on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
Rebuild.run(args, "x86_64", configuration, report=False, unsafe=False) Rebuild.run(args, "x86_64", configuration, report=False, unsafe=False)
extract_mock.assert_called_once_with(pytest.helpers.anyvar(int), from_database=args.from_database) extract_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.status, from_database=args.from_database)
application_packages_mock.assert_called_once_with([package_ahriman], None) application_packages_mock.assert_called_once_with([package_ahriman], None)
application_mock.assert_called_once_with([package_ahriman]) application_mock.assert_called_once_with([package_ahriman])
check_mock.assert_has_calls([MockCall(False, False), MockCall(False, False)]) check_mock.assert_has_calls([MockCall(False, False), MockCall(False, False)])
@ -56,7 +58,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
def test_run_extract_packages(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run_extract_packages(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """
must run command must run command from database
""" """
args = _default_args(args) args = _default_args(args)
args.from_database = True args.from_database = True
@ -66,7 +68,7 @@ def test_run_extract_packages(args: argparse.Namespace, configuration: Configura
extract_mock = mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[]) extract_mock = mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[])
Rebuild.run(args, "x86_64", configuration, report=False, unsafe=False) Rebuild.run(args, "x86_64", configuration, report=False, unsafe=False)
extract_mock.assert_called_once_with(pytest.helpers.anyvar(int), from_database=args.from_database) extract_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.status, from_database=args.from_database)
def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
@ -156,7 +158,19 @@ def test_extract_packages(application: Application, mocker: MockerFixture) -> No
must extract packages from database must extract packages from database
""" """
packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages") packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages")
Rebuild.extract_packages(application, from_database=False) Rebuild.extract_packages(application, None, from_database=False)
packages_mock.assert_called_once_with()
def test_extract_packages_by_status(application: Application, mocker: MockerFixture) -> None:
"""
must extract packages from database and filter them by status
"""
packages_mock = mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=[
("package1", BuildStatus(BuildStatusEnum.Success)),
("package2", BuildStatus(BuildStatusEnum.Failed)),
])
assert Rebuild.extract_packages(application, BuildStatusEnum.Failed, from_database=True) == ["package2"]
packages_mock.assert_called_once_with() packages_mock.assert_called_once_with()
@ -165,5 +179,5 @@ def test_extract_packages_from_database(application: Application, mocker: Mocker
must extract packages from database must extract packages from database
""" """
packages_mock = mocker.patch("ahriman.core.database.SQLite.packages_get") packages_mock = mocker.patch("ahriman.core.database.SQLite.packages_get")
Rebuild.extract_packages(application, from_database=True) Rebuild.extract_packages(application, None, from_database=True)
packages_mock.assert_called_once_with() packages_mock.assert_called_once_with()

View File

@ -25,7 +25,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
""" """
args.build_as_user = "ahriman" args.build_as_user = "ahriman"
args.build_command = "ahriman" args.build_command = "ahriman"
args.from_configuration = Path("/usr/share/devtools/pacman-extra.conf") args.from_configuration = Path("/usr/share/devtools/pacman.conf.d/extra.conf")
args.makeflags_jobs = True args.makeflags_jobs = True
args.mirror = "mirror" args.mirror = "mirror"
args.multilib = True args.multilib = True

View File

@ -61,7 +61,14 @@ def test_schema(configuration: Configuration) -> None:
assert schema.pop("console") assert schema.pop("console")
assert schema.pop("email") assert schema.pop("email")
assert schema.pop("github") assert schema.pop("github")
assert schema.pop("gitremote")
assert schema.pop("html") assert schema.pop("html")
assert schema.pop("keyring")
assert schema.pop("keyring_generator")
assert schema.pop("mirrorlist")
assert schema.pop("mirrorlist_generator")
assert schema.pop("remote-pull")
assert schema.pop("remote-push")
assert schema.pop("report") assert schema.pop("report")
assert schema.pop("rsync") assert schema.pop("rsync")
assert schema.pop("s3") assert schema.pop("s3")
@ -76,6 +83,7 @@ def test_schema_invalid_trigger(configuration: Configuration) -> None:
must skip trigger if it caused exception on load must skip trigger if it caused exception on load
""" """
configuration.set_option("build", "triggers", "some.invalid.trigger.path.Trigger") configuration.set_option("build", "triggers", "some.invalid.trigger.path.Trigger")
configuration.remove_option("build", "triggers_known")
assert Validate.schema("x86_64", configuration) == CONFIGURATION_SCHEMA assert Validate.schema("x86_64", configuration) == CONFIGURATION_SCHEMA

View File

@ -6,6 +6,7 @@ from pytest_mock import MockerFixture
from ahriman.application.handlers import Web from ahriman.application.handlers import Web
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.models.log_handler import LogHandler
def _default_args(args: argparse.Namespace) -> argparse.Namespace: def _default_args(args: argparse.Namespace) -> argparse.Namespace:
@ -19,6 +20,11 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
argparse.Namespace: generated arguments for these test cases argparse.Namespace: generated arguments for these test cases
""" """
args.parser = lambda: True args.parser = lambda: True
args.force = False
args.log_handler = None
args.report = True
args.quiet = False
args.unsafe = False
return args return args
@ -43,6 +49,63 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
join_mock.assert_called_once_with() join_mock.assert_called_once_with()
def test_extract_arguments(args: argparse.Namespace, configuration: Configuration):
"""
must extract correct args
"""
expected = [
"--architecture", "x86_64",
"--configuration", str(configuration.path),
]
probe = _default_args(args)
assert list(Web.extract_arguments(probe, "x86_64", configuration)) == expected
probe.force = True
expected.extend(["--force"])
assert list(Web.extract_arguments(probe, "x86_64", configuration)) == expected
probe.log_handler = LogHandler.Console
expected.extend(["--log-handler", probe.log_handler.value])
assert list(Web.extract_arguments(probe, "x86_64", configuration)) == expected
probe.quiet = True
expected.extend(["--quiet"])
assert list(Web.extract_arguments(probe, "x86_64", configuration)) == expected
probe.unsafe = True
expected.extend(["--unsafe"])
assert list(Web.extract_arguments(probe, "x86_64", configuration)) == expected
def test_extract_arguments_full(parser: argparse.ArgumentParser, configuration: Configuration):
"""
must extract all available args except for blacklisted
"""
# append all options from parser
args = argparse.Namespace()
for action in parser._actions:
if action.default == argparse.SUPPRESS:
continue
# extract option from the following list
value = action.const or \
next(iter(action.choices or []), None) or \
(not action.default if isinstance(action.default, bool) else None) or \
"random string"
if action.type is not None:
value = action.type(value)
setattr(args, action.dest, value)
assert list(Web.extract_arguments(args, "x86_64", configuration)) == [
"--architecture", "x86_64",
"--configuration", str(configuration.path),
"--force",
"--log-handler", "console",
"--quiet",
"--unsafe",
]
def test_disallow_auto_architecture_run() -> None: def test_disallow_auto_architecture_run() -> None:
""" """
must not allow auto architecture run must not allow auto architecture run

View File

@ -6,6 +6,7 @@ from pytest_mock import MockerFixture
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.models.action import Action from ahriman.models.action import Action
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.log_handler import LogHandler
from ahriman.models.sign_settings import SignSettings from ahriman.models.sign_settings import SignSettings
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
@ -37,6 +38,14 @@ def test_parser_option_lock(parser: argparse.ArgumentParser) -> None:
assert isinstance(args.lock, Path) assert isinstance(args.lock, Path)
def test_parser_option_log_handler(parser: argparse.ArgumentParser) -> None:
"""
must convert log-handler option to LogHandler instance
"""
args = parser.parse_args(["--log-handler", "console", "service-config"])
assert isinstance(args.log_handler, LogHandler)
def test_multiple_architectures(parser: argparse.ArgumentParser) -> None: def test_multiple_architectures(parser: argparse.ArgumentParser) -> None:
""" """
must accept multiple architectures must accept multiple architectures
@ -373,6 +382,42 @@ def test_subparsers_repo_check_option_refresh(parser: argparse.ArgumentParser) -
assert args.refresh == 2 assert args.refresh == 2
def test_subparsers_repo_create_keyring(parser: argparse.ArgumentParser) -> None:
"""
repo-create-keyring command must imply trigger
"""
args = parser.parse_args(["repo-create-keyring"])
assert args.trigger == ["ahriman.core.support.KeyringTrigger"]
def test_subparsers_repo_create_keyring_architecture(parser: argparse.ArgumentParser) -> None:
"""
repo-create-keyring command must correctly parse architecture list
"""
args = parser.parse_args(["repo-create-keyring"])
assert args.architecture is None
args = parser.parse_args(["-a", "x86_64", "repo-create-keyring"])
assert args.architecture == ["x86_64"]
def test_subparsers_repo_create_mirrorlist(parser: argparse.ArgumentParser) -> None:
"""
repo-create-mirrorlist command must imply trigger
"""
args = parser.parse_args(["repo-create-mirrorlist"])
assert args.trigger == ["ahriman.core.support.MirrorlistTrigger"]
def test_subparsers_repo_create_mirrorlist_architecture(parser: argparse.ArgumentParser) -> None:
"""
repo-create-mirrorlist command must correctly parse architecture list
"""
args = parser.parse_args(["repo-create-mirrorlist"])
assert args.architecture is None
args = parser.parse_args(["-a", "x86_64", "repo-create-mirrorlist"])
assert args.architecture == ["x86_64"]
def test_subparsers_repo_daemon(parser: argparse.ArgumentParser) -> None: def test_subparsers_repo_daemon(parser: argparse.ArgumentParser) -> None:
""" """
repo-daemon command must imply dry run, exit code and package repo-daemon command must imply dry run, exit code and package
@ -415,6 +460,14 @@ def test_subparsers_repo_rebuild_architecture(parser: argparse.ArgumentParser) -
assert args.architecture == ["x86_64"] assert args.architecture == ["x86_64"]
def test_subparsers_repo_rebuild_option_status(parser: argparse.ArgumentParser) -> None:
"""
repo-rebuild command must convert status option to BuildStatusEnum instance
"""
args = parser.parse_args(["-a", "x86_64", "repo-rebuild", "--status", "failed"])
assert isinstance(args.status, BuildStatusEnum)
def test_subparsers_repo_remove_unknown_architecture(parser: argparse.ArgumentParser) -> None: def test_subparsers_repo_remove_unknown_architecture(parser: argparse.ArgumentParser) -> None:
""" """
repo-remove-unknown command must correctly parse architecture list repo-remove-unknown command must correctly parse architecture list
@ -488,7 +541,7 @@ def test_subparsers_repo_status_update(parser: argparse.ArgumentParser) -> None:
def test_subparsers_repo_status_update_option_status(parser: argparse.ArgumentParser) -> None: def test_subparsers_repo_status_update_option_status(parser: argparse.ArgumentParser) -> None:
""" """
repo-status-update command must convert status option to buildstatusenum instance repo-status-update command must convert status option to BuildStatusEnum instance
""" """
args = parser.parse_args(["-a", "x86_64", "repo-status-update"]) args = parser.parse_args(["-a", "x86_64", "repo-status-update"])
assert isinstance(args.status, BuildStatusEnum) assert isinstance(args.status, BuildStatusEnum)
@ -655,7 +708,7 @@ def test_subparsers_service_setup_option_from_configuration(parser: argparse.Arg
def test_subparsers_service_setup_option_sign_target(parser: argparse.ArgumentParser) -> None: def test_subparsers_service_setup_option_sign_target(parser: argparse.ArgumentParser) -> None:
""" """
service-setup command must convert sign-target option to signsettings instance service-setup command must convert sign-target option to SignSettings instance
""" """
args = parser.parse_args(["-a", "x86_64", "service-setup", "--packager", "John Doe <john@doe.com>", args = parser.parse_args(["-a", "x86_64", "service-setup", "--packager", "John Doe <john@doe.com>",
"--repository", "aur-clone", "--sign-target", "packages"]) "--repository", "aur-clone", "--sign-target", "packages"])
@ -694,7 +747,7 @@ def test_subparsers_user_add_architecture(parser: argparse.ArgumentParser) -> No
def test_subparsers_user_add_option_role(parser: argparse.ArgumentParser) -> None: def test_subparsers_user_add_option_role(parser: argparse.ArgumentParser) -> None:
""" """
user-add command must convert role option to useraccess instance user-add command must convert role option to UserAccess instance
""" """
args = parser.parse_args(["user-add", "username"]) args = parser.parse_args(["user-add", "username"])
assert isinstance(args.role, UserAccess) assert isinstance(args.role, UserAccess)
@ -726,7 +779,7 @@ def test_subparsers_user_list_architecture(parser: argparse.ArgumentParser) -> N
def test_subparsers_user_list_option_role(parser: argparse.ArgumentParser) -> None: def test_subparsers_user_list_option_role(parser: argparse.ArgumentParser) -> None:
""" """
user-list command must convert role option to useraccess instance user-list command must convert role option to UserAccess instance
""" """
args = parser.parse_args(["user-list", "--role", "full"]) args = parser.parse_args(["user-list", "--role", "full"])
assert isinstance(args.role, UserAccess) assert isinstance(args.role, UserAccess)

View File

@ -146,6 +146,9 @@ def aur_package_ahriman() -> AURPackage:
"rsync", "rsync",
"subversion", "subversion",
], ],
check_depends=[
"python-pytest",
],
conflicts=[], conflicts=[],
provides=[], provides=[],
license=["GPL3"], license=["GPL3"],
@ -335,6 +338,9 @@ def package_description_ahriman() -> PackageDescription:
"rsync", "rsync",
"subversion", "subversion",
], ],
check_depends=[
"python-pytest",
],
description="ArcH linux ReposItory MANager", description="ArcH linux ReposItory MANager",
filename="ahriman-2.6.0-1-any.pkg.tar.zst", filename="ahriman-2.6.0-1-any.pkg.tar.zst",
groups=[], groups=[],
@ -482,7 +488,7 @@ def spawner(configuration: Configuration) -> Spawn:
Returns: Returns:
Spawn: spawner fixture Spawn: spawner fixture
""" """
return Spawn(MagicMock(), "x86_64", configuration) return Spawn(MagicMock(), "x86_64", ["--architecture", "x86_64", "--configuration", str(configuration.path)])
@pytest.fixture @pytest.fixture

View File

@ -50,19 +50,7 @@ def test_remote_git_url(aur_package_akonadi: AURPackage) -> None:
Official.remote_git_url(aur_package_akonadi.package_base, repository) Official.remote_git_url(aur_package_akonadi.package_base, repository)
for repository in ("core", "extra", "Core", "Extra") for repository in ("core", "extra", "Core", "Extra")
] ]
assert all(git_url.endswith("svntogit-packages.git") for git_url in git_urls) assert all(git_url.endswith(f"{aur_package_akonadi.package_base}.git") for git_url in git_urls)
assert len(set(git_urls)) == 1
def test_remote_git_url_community(aur_package_akonadi: AURPackage) -> None:
"""
must generate package git url for core packages
"""
git_urls = [
Official.remote_git_url(aur_package_akonadi.package_base, repository)
for repository in ("community", "multilib", "Community", "Multilib")
]
assert all(git_url.endswith("svntogit-community.git") for git_url in git_urls)
assert len(set(git_urls)) == 1 assert len(set(git_urls)) == 1

View File

@ -135,12 +135,17 @@ def test_init(mocker: MockerFixture) -> None:
""" """
must create empty repository at the specified path must create empty repository at the specified path
""" """
mocker.patch("ahriman.models.package.Package.local_files", return_value=[Path("local")])
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add")
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit")
local = Path("local") local = Path("local")
Sources.init(local) Sources.init(local)
check_output_mock.assert_called_once_with("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH, check_output_mock.assert_called_once_with("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH,
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=pytest.helpers.anyvar(int))
add_mock.assert_called_once_with(local, "PKGBUILD", ".SRCINFO", "local")
commit_mock.assert_called_once_with(local, author="ahriman <ahriman@localhost>")
def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:

View File

@ -10,6 +10,13 @@ from ahriman.core.exceptions import InitializeError
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
def test_repository_name(configuration: Configuration) -> None:
"""
must return valid repository name
"""
assert configuration.repository_name == "aur-clone"
def test_repository_paths(configuration: Configuration, repository_paths: RepositoryPaths) -> None: def test_repository_paths(configuration: Configuration, repository_paths: RepositoryPaths) -> None:
""" """
must return repository paths must return repository paths

View File

@ -73,6 +73,30 @@ def test_validate_is_ip_address(validator: Validator, mocker: MockerFixture) ->
]) ])
def test_validate_path_is_absolute(validator: Validator, mocker: MockerFixture) -> None:
"""
must validate that path is absolute
"""
error_mock = mocker.patch("ahriman.core.configuration.validator.Validator._error")
mocker.patch("pathlib.Path.is_absolute", return_value=False)
validator._validate_path_is_absolute(False, "field", Path("1"))
mocker.patch("pathlib.Path.is_absolute", return_value=True)
validator._validate_path_is_absolute(False, "field", Path("2"))
mocker.patch("pathlib.Path.is_absolute", return_value=False)
validator._validate_path_is_absolute(True, "field", Path("3"))
mocker.patch("pathlib.Path.is_absolute", return_value=True)
validator._validate_path_is_absolute(True, "field", Path("4"))
error_mock.assert_has_calls([
MockCall("field", "Path 2 must be relative"),
MockCall("field", "Path 3 must be absolute"),
])
def test_validate_is_url(validator: Validator, mocker: MockerFixture) -> None: def test_validate_is_url(validator: Validator, mocker: MockerFixture) -> None:
""" """
must validate url correctly must validate url correctly
@ -105,12 +129,16 @@ def test_validate_path_exists(validator: Validator, mocker: MockerFixture) -> No
mocker.patch("pathlib.Path.exists", return_value=False) mocker.patch("pathlib.Path.exists", return_value=False)
validator._validate_path_exists(False, "field", Path("1")) validator._validate_path_exists(False, "field", Path("1"))
mocker.patch("pathlib.Path.exists", return_value=False)
validator._validate_path_exists(True, "field", Path("2"))
mocker.patch("pathlib.Path.exists", return_value=True) mocker.patch("pathlib.Path.exists", return_value=True)
validator._validate_path_exists(False, "field", Path("2"))
mocker.patch("pathlib.Path.exists", return_value=False)
validator._validate_path_exists(True, "field", Path("3")) validator._validate_path_exists(True, "field", Path("3"))
mocker.patch("pathlib.Path.exists", return_value=True)
validator._validate_path_exists(True, "field", Path("4"))
error_mock.assert_has_calls([ error_mock.assert_has_calls([
MockCall("field", "Path 2 must exist"), MockCall("field", "Path 2 must not exist"),
MockCall("field", "Path 3 must exist"),
]) ])

View File

@ -4,6 +4,7 @@ import pytest
from ahriman.core.alpm.repo import Repo from ahriman.core.alpm.repo import Repo
from ahriman.core.build_tools.task import Task from ahriman.core.build_tools.task import Task
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG
from ahriman.core.tree import Leaf from ahriman.core.tree import Leaf
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -63,6 +64,20 @@ def repo(configuration: Configuration, repository_paths: RepositoryPaths) -> Rep
return Repo(configuration.get("repository", "name"), repository_paths, []) return Repo(configuration.get("repository", "name"), repository_paths, [])
@pytest.fixture
def gpg(configuration: Configuration) -> GPG:
"""
fixture for empty GPG
Args:
configuration(Configuration): configuration fixture
Returns:
GPG: GPG test instance
"""
return GPG(configuration)
@pytest.fixture @pytest.fixture
def task_ahriman(package_ahriman: Package, configuration: Configuration, repository_paths: RepositoryPaths) -> Task: def task_ahriman(package_ahriman: Package, configuration: Configuration, repository_paths: RepositoryPaths) -> Task:
""" """

View File

@ -0,0 +1,52 @@
import pytest
from pytest_mock import MockerFixture
from sqlite3 import Connection
from ahriman.core.configuration import Configuration
from ahriman.core.database.migrations.m007_check_depends import migrate_data, migrate_package_check_depends, steps
from ahriman.models.package import Package
def test_migration_check_depends() -> None:
"""
migration must not be empty
"""
assert steps
def test_migrate_data(connection: Connection, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must perform data migration
"""
depends_mock = mocker.patch("ahriman.core.database.migrations.m007_check_depends.migrate_package_check_depends")
migrate_data(connection, configuration)
depends_mock.assert_called_once_with(connection, configuration)
def test_migrate_package_depends(connection: Connection, configuration: Configuration, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must update make and opt depends list
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.packages[package_ahriman.base].filepath])
package_mock = mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
migrate_package_check_depends(connection, configuration)
package_mock.assert_called_once_with(
package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int), remote=None)
connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{
"check_depends": package_ahriman.packages[package_ahriman.base].check_depends,
"package": package_ahriman.base,
}])
def test_migrate_package_depends_skip(connection: Connection, configuration: Configuration,
mocker: MockerFixture) -> None:
"""
must skip update make and opt depends list if no repository directory found
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
migrate_package_check_depends(connection, configuration)
connection.executemany.assert_not_called()

View File

@ -0,0 +1,31 @@
import sys
from pytest_mock import MockerFixture
# because of how imports work it must be first test
def test_dummy_journal_handler(mocker: MockerFixture) -> None:
"""
must import dummy journal handler if upstream systemd was not found
"""
mocker.patch.dict(sys.modules, {"systemd.journal": None})
from logging import NullHandler
from ahriman.core.log.journal_handler import JournalHandler
assert issubclass(JournalHandler, NullHandler)
def test_init() -> None:
"""
must init dummy handler
"""
from ahriman.core.log.journal_handler import _JournalHandler
assert _JournalHandler(42, answer=42)
def test_journal_handler() -> None:
"""
must import journal handler
"""
from systemd.journal import JournalHandler as UpstreamJournalHandler
from ahriman.core.log.journal_handler import JournalHandler
assert JournalHandler is UpstreamJournalHandler

View File

@ -1,21 +1,59 @@
import logging import logging
import pytest
import sys
from logging.config import fileConfig
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from systemd.journal import JournalHandler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.log import Log from ahriman.core.log import Log
from ahriman.models.log_handler import LogHandler
def test_handler() -> None:
"""
must extract journald handler if available
"""
assert Log.handler(None) == LogHandler.Journald
def test_handler_selected() -> None:
"""
must return selected log handler
"""
assert Log.handler(LogHandler.Console) == LogHandler.Console
def test_handler_syslog(mocker: MockerFixture) -> None:
"""
must return syslog handler if no journal is available
"""
mocker.patch("pathlib.Path.exists", return_value=True)
mocker.patch.dict(sys.modules, {"systemd.journal": None})
assert Log.handler(None) == LogHandler.Syslog
def test_handler_console(mocker: MockerFixture) -> None:
"""
must return console handler if no journal is available and no log device was found
"""
mocker.patch("pathlib.Path.exists", return_value=False)
mocker.patch.dict(sys.modules, {"systemd.journal": None})
assert Log.handler(None) == LogHandler.Console
def test_load(configuration: Configuration, mocker: MockerFixture) -> None: def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
""" """
must load logging must load logging
""" """
logging_mock = mocker.patch("ahriman.core.log.log.fileConfig") logging_mock = mocker.patch("ahriman.core.log.log.fileConfig", side_effect=fileConfig)
http_log_mock = mocker.patch("ahriman.core.log.http_log_handler.HttpLogHandler.load") http_log_mock = mocker.patch("ahriman.core.log.http_log_handler.HttpLogHandler.load")
Log.load(configuration, quiet=False, report=False) Log.load(configuration, LogHandler.Journald, quiet=False, report=False)
logging_mock.assert_called_once_with(configuration.logging_path) logging_mock.assert_called_once_with(pytest.helpers.anyvar(int), disable_existing_loggers=True)
http_log_mock.assert_called_once_with(configuration, report=False) http_log_mock.assert_called_once_with(configuration, report=False)
assert all(isinstance(handler, JournalHandler) for handler in logging.getLogger().handlers)
def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> None: def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> None:
@ -23,7 +61,7 @@ def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> N
must fall back to stderr without errors must fall back to stderr without errors
""" """
mocker.patch("ahriman.core.log.log.fileConfig", side_effect=PermissionError()) mocker.patch("ahriman.core.log.log.fileConfig", side_effect=PermissionError())
Log.load(configuration, quiet=False, report=False) Log.load(configuration, LogHandler.Journald, quiet=False, report=False)
def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None: def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None:
@ -31,5 +69,5 @@ def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None
must disable logging in case if quiet flag set must disable logging in case if quiet flag set
""" """
disable_mock = mocker.patch("logging.disable") disable_mock = mocker.patch("logging.disable")
Log.load(configuration, quiet=True, report=False) Log.load(configuration, LogHandler.Journald, quiet=True, report=False)
disable_mock.assert_called_once_with(logging.WARNING) disable_mock.assert_called_once_with(logging.WARNING)

View File

@ -16,4 +16,4 @@ def test_generate(configuration: Configuration, package_ahriman: Package, mocker
report = HTML("x86_64", configuration, "html") report = HTML("x86_64", configuration, "html")
report.generate([package_ahriman], Result()) report.generate([package_ahriman], Result())
write_mock.assert_called_once_with(pytest.helpers.anyvar(int)) write_mock.assert_called_once_with(pytest.helpers.anyvar(int), encoding="utf8")

View File

@ -1,23 +1,8 @@
import pytest import pytest
from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG from ahriman.core.sign.gpg import GPG
@pytest.fixture
def gpg(configuration: Configuration) -> GPG:
"""
fixture for empty GPG
Args:
configuration(Configuration): configuration fixture
Returns:
GPG: GPG test instance
"""
return GPG("x86_64", configuration)
@pytest.fixture @pytest.fixture
def gpg_with_key(gpg: GPG) -> GPG: def gpg_with_key(gpg: GPG) -> GPG:
""" """

View File

@ -97,6 +97,33 @@ def test_key_download_failure(gpg: GPG, mocker: MockerFixture) -> None:
gpg.key_download("keyserver.ubuntu.com", "0xE989490C") gpg.key_download("keyserver.ubuntu.com", "0xE989490C")
def test_key_export(gpg: GPG, mocker: MockerFixture) -> None:
"""
must export gpg key correctly
"""
check_output_mock = mocker.patch("ahriman.core.sign.gpg.GPG._check_output", return_value="key")
assert gpg.key_export("k") == "key"
check_output_mock.assert_called_once_with("gpg", "--armor", "--no-emit-version", "--export", "k",
logger=pytest.helpers.anyvar(int))
def test_key_fingerprint(gpg: GPG, mocker: MockerFixture) -> None:
"""
must extract fingerprint
"""
check_output_mock = mocker.patch(
"ahriman.core.sign.gpg.GPG._check_output",
return_value="""tru::1:1576103830:0:3:1:5
fpr:::::::::C6EBB9222C3C8078631A0DE4BD2AC8C5E989490C:
sub:-:4096:1:7E3A4240CE3C45C2:1615121387::::::e::::::23:
fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:""")
key = "0xCE3C45C2"
assert gpg.key_fingerprint(key) == "C6EBB9222C3C8078631A0DE4BD2AC8C5E989490C"
check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key,
logger=pytest.helpers.anyvar(int))
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None: def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
""" """
must import PGP key from the server must import PGP key from the server
@ -108,6 +135,21 @@ def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
check_output_mock.assert_called_once_with("gpg", "--import", input_data="key", logger=pytest.helpers.anyvar(int)) check_output_mock.assert_called_once_with("gpg", "--import", input_data="key", logger=pytest.helpers.anyvar(int))
def test_keys(gpg: GPG) -> None:
"""
must extract keys
"""
assert gpg.keys() == []
gpg.default_key = "key"
assert gpg.keys() == [gpg.default_key]
gpg.configuration.set_option("sign", "key_a", "key1")
gpg.configuration.set_option("sign", "key_b", "key1")
gpg.configuration.set_option("sign", "key_c", "key2")
assert gpg.keys() == ["key", "key1", "key2"]
def test_process(gpg_with_key: GPG, mocker: MockerFixture) -> None: def test_process(gpg_with_key: GPG, mocker: MockerFixture) -> None:
""" """
must call process method correctly must call process method correctly

View File

@ -55,7 +55,7 @@ def test_create_session(web_client: WebClient, mocker: MockerFixture) -> None:
session = web_client._create_session(use_unix_socket=False) session = web_client._create_session(use_unix_socket=False)
assert isinstance(session, requests.Session) assert isinstance(session, requests.Session)
assert not isinstance(session, requests_unixsocket.Session) assert not isinstance(session, requests_unixsocket.Session)
login_mock.assert_called_once_with() login_mock.assert_called_once_with(pytest.helpers.anyvar(int))
def test_create_session_unix_socket(web_client: WebClient, mocker: MockerFixture) -> None: def test_create_session_unix_socket(web_client: WebClient, mocker: MockerFixture) -> None:
@ -80,7 +80,7 @@ def test_login(web_client: WebClient, user: User, mocker: MockerFixture) -> None
"password": user.password "password": user.password
} }
web_client._login() web_client._login(requests.Session())
requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), json=payload) requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), json=payload)
@ -90,7 +90,7 @@ def test_login_failed(web_client: WebClient, user: User, mocker: MockerFixture)
""" """
web_client.user = user web_client.user = user
mocker.patch("requests.Session.post", side_effect=Exception()) mocker.patch("requests.Session.post", side_effect=Exception())
web_client._login() web_client._login(requests.Session())
def test_login_failed_http_error(web_client: WebClient, user: User, mocker: MockerFixture) -> None: def test_login_failed_http_error(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
@ -99,7 +99,7 @@ def test_login_failed_http_error(web_client: WebClient, user: User, mocker: Mock
""" """
web_client.user = user web_client.user = user
mocker.patch("requests.Session.post", side_effect=requests.exceptions.HTTPError()) mocker.patch("requests.Session.post", side_effect=requests.exceptions.HTTPError())
web_client._login() web_client._login(requests.Session())
def test_login_skip(web_client: WebClient, mocker: MockerFixture) -> None: def test_login_skip(web_client: WebClient, mocker: MockerFixture) -> None:
@ -107,7 +107,7 @@ def test_login_skip(web_client: WebClient, mocker: MockerFixture) -> None:
must skip login if no user set must skip login if no user set
""" """
requests_mock = mocker.patch("requests.Session.post") requests_mock = mocker.patch("requests.Session.post")
web_client._login() web_client._login(requests.Session())
requests_mock.assert_not_called() requests_mock.assert_not_called()

View File

@ -0,0 +1,34 @@
import pytest
from ahriman.core.configuration import Configuration
from ahriman.core.support.package_creator import PackageCreator
from ahriman.core.support.pkgbuild.mirrorlist_generator import MirrorlistGenerator
@pytest.fixture
def mirrorlist_generator(configuration: Configuration) -> MirrorlistGenerator:
"""
fixture for mirrorlist pkgbuild generator
Args:
configuration(Configuration): configuration fixture
Returns:
MirrorlistGenerator: mirrorlist pkgbuild generator test instance
"""
return MirrorlistGenerator(configuration, "mirrorlist")
@pytest.fixture
def package_creator(configuration: Configuration, mirrorlist_generator: MirrorlistGenerator) -> PackageCreator:
"""
package creator fixture
Args:
configuration(Configuration): configuration fixture
mirrorlist_generator(MirrorlistGenerator):
Returns:
PackageCreator: package creator test instance
"""
return PackageCreator(configuration, mirrorlist_generator)

View File

@ -0,0 +1,32 @@
import pytest
from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
@pytest.fixture
def keyring_generator(gpg: GPG, configuration: Configuration) -> KeyringGenerator:
"""
fixture for keyring pkgbuild generator
Args:
gpg(GPG): empty GPG fixture
configuration(Configuration): configuration fixture
Returns:
KeyringGenerator: keyring generator test instance
"""
return KeyringGenerator(gpg, configuration, "keyring")
@pytest.fixture
def pkgbuild_generator() -> PkgbuildGenerator:
"""
fixture for dummy pkgbuild generator
Returns:
PkgbuildGenerator: pkgbuild generator test instance
"""
return PkgbuildGenerator()

View File

@ -0,0 +1,185 @@
import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call as MockCall
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import PkgbuildGeneratorError
from ahriman.core.sign.gpg import GPG
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
def test_init_packagers(gpg: GPG, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must extract packagers keys
"""
mocker.patch("ahriman.core.sign.gpg.GPG.keys", return_value=["key"])
assert KeyringGenerator(gpg, configuration, "keyring").packagers == ["key"]
configuration.set_option("keyring", "packagers", "key1")
assert KeyringGenerator(gpg, configuration, "keyring").packagers == ["key1"]
def test_init_revoked(gpg: GPG, configuration: Configuration) -> None:
"""
must extract revoked keys
"""
assert KeyringGenerator(gpg, configuration, "keyring").revoked == []
configuration.set_option("keyring", "revoked", "key1")
assert KeyringGenerator(gpg, configuration, "keyring").revoked == ["key1"]
def test_init_trusted(gpg: GPG, configuration: Configuration) -> None:
"""
must extract trusted keys
"""
assert KeyringGenerator(gpg, configuration, "keyring").trusted == []
gpg.default_key = "key"
assert KeyringGenerator(gpg, configuration, "keyring").trusted == ["key"]
configuration.set_option("keyring", "trusted", "key1")
assert KeyringGenerator(gpg, configuration, "keyring").trusted == ["key1"]
def test_license(gpg: GPG, configuration: Configuration) -> None:
"""
must generate correct licenses list
"""
assert KeyringGenerator(gpg, configuration, "keyring").license == ["Unlicense"]
configuration.set_option("keyring", "license", "GPL MPL")
assert KeyringGenerator(gpg, configuration, "keyring").license == ["GPL", "MPL"]
def test_pkgdesc(gpg: GPG, configuration: Configuration) -> None:
"""
must generate correct pkgdesc property
"""
assert KeyringGenerator(gpg, configuration, "keyring").pkgdesc == "aur-clone PGP keyring"
configuration.set_option("keyring", "description", "description")
assert KeyringGenerator(gpg, configuration, "keyring").pkgdesc == "description"
def test_pkgname(gpg: GPG, configuration: Configuration) -> None:
"""
must generate correct pkgname property
"""
assert KeyringGenerator(gpg, configuration, "keyring").pkgname == "aur-clone-keyring"
configuration.set_option("keyring", "package", "keyring")
assert KeyringGenerator(gpg, configuration, "keyring").pkgname == "keyring"
def test_url(gpg: GPG, configuration: Configuration) -> None:
"""
must generate correct url property
"""
assert KeyringGenerator(gpg, configuration, "keyring").url == ""
configuration.set_option("keyring", "homepage", "homepage")
assert KeyringGenerator(gpg, configuration, "keyring").url == "homepage"
def test_generate_gpg(keyring_generator: KeyringGenerator, mocker: MockerFixture) -> None:
"""
must correctly generate file with all PGP keys
"""
file_mock = MagicMock()
export_mock = mocker.patch("ahriman.core.sign.gpg.GPG.key_export", side_effect=lambda key: key)
open_mock = mocker.patch("pathlib.Path.open")
open_mock.return_value.__enter__.return_value = file_mock
keyring_generator.packagers = ["key"]
keyring_generator.revoked = ["revoked"]
keyring_generator.trusted = ["trusted", "key"]
keyring_generator._generate_gpg(Path("local"))
open_mock.assert_called_once_with("w")
export_mock.assert_has_calls([MockCall("key"), MockCall("revoked"), MockCall("trusted")])
file_mock.write.assert_has_calls([
MockCall("key"), MockCall("\n"),
MockCall("revoked"), MockCall("\n"),
MockCall("trusted"), MockCall("\n"),
])
def test_generate_revoked(keyring_generator: KeyringGenerator, mocker: MockerFixture) -> None:
"""
must correctly generate file with revoked keys
"""
file_mock = MagicMock()
fingerprint_mock = mocker.patch("ahriman.core.sign.gpg.GPG.key_fingerprint", side_effect=lambda key: key)
open_mock = mocker.patch("pathlib.Path.open")
open_mock.return_value.__enter__.return_value = file_mock
keyring_generator.revoked = ["revoked"]
keyring_generator._generate_revoked(Path("local"))
open_mock.assert_called_once_with("w")
fingerprint_mock.assert_called_once_with("revoked")
file_mock.write.assert_has_calls([MockCall("revoked"), MockCall("\n")])
def test_generate_trusted(keyring_generator: KeyringGenerator, mocker: MockerFixture) -> None:
"""
must correctly generate file with trusted keys
"""
file_mock = MagicMock()
fingerprint_mock = mocker.patch("ahriman.core.sign.gpg.GPG.key_fingerprint", side_effect=lambda key: key)
open_mock = mocker.patch("pathlib.Path.open")
open_mock.return_value.__enter__.return_value = file_mock
keyring_generator.trusted = ["trusted", "trusted"]
keyring_generator._generate_trusted(Path("local"))
open_mock.assert_called_once_with("w")
fingerprint_mock.assert_called_once_with("trusted")
file_mock.write.assert_has_calls([MockCall("trusted"), MockCall(":4:\n")])
def test_generate_trusted_empty(keyring_generator: KeyringGenerator) -> None:
"""
must raise PkgbuildGeneratorError if no trusted keys set
"""
with pytest.raises(PkgbuildGeneratorError):
keyring_generator._generate_trusted(Path("local"))
def test_install(keyring_generator: KeyringGenerator) -> None:
"""
must return install functions
"""
assert keyring_generator.install() == """post_upgrade() {
if usr/bin/pacman-key -l >/dev/null 2>&1; then
usr/bin/pacman-key --populate aur-clone
usr/bin/pacman-key --updatedb
fi
}
post_install() {
if [ -x usr/bin/pacman-key ]; then
post_upgrade
fi
}"""
def test_package(keyring_generator: KeyringGenerator) -> None:
"""
must generate package function correctly
"""
assert keyring_generator.package() == """{
install -Dm644 "$srcdir/aur-clone.gpg" "$pkgdir/usr/share/pacman/keyrings/aur-clone.gpg"
install -Dm644 "$srcdir/aur-clone-revoked" "$pkgdir/usr/share/pacman/keyrings/aur-clone-revoked"
install -Dm644 "$srcdir/aur-clone-trusted" "$pkgdir/usr/share/pacman/keyrings/aur-clone-trusted"
}"""
def test_sources(keyring_generator: KeyringGenerator) -> None:
"""
must return valid sources files list
"""
assert keyring_generator.sources().get("aur-clone.gpg")
assert keyring_generator.sources().get("aur-clone-revoked")
assert keyring_generator.sources().get("aur-clone-trusted")

View File

@ -0,0 +1,90 @@
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.support.pkgbuild.mirrorlist_generator import MirrorlistGenerator
def test_init_path(configuration: Configuration) -> None:
"""
must set relative path to mirrorlist
"""
assert MirrorlistGenerator(configuration, "mirrorlist").path == Path("etc") / "pacman.d" / "aur-clone-mirrorlist"
configuration.set_option("mirrorlist", "path", "/etc")
assert MirrorlistGenerator(configuration, "mirrorlist").path == Path("etc")
def test_license(configuration: Configuration) -> None:
"""
must generate correct licenses list
"""
assert MirrorlistGenerator(configuration, "mirrorlist").license == ["Unlicense"]
configuration.set_option("mirrorlist", "license", "GPL MPL")
assert MirrorlistGenerator(configuration, "mirrorlist").license == ["GPL", "MPL"]
def test_pkgdesc(configuration: Configuration) -> None:
"""
must generate correct pkgdesc property
"""
assert MirrorlistGenerator(configuration, "mirrorlist").pkgdesc == "aur-clone mirror list for use by pacman"
configuration.set_option("mirrorlist", "description", "description")
assert MirrorlistGenerator(configuration, "mirrorlist").pkgdesc == "description"
def test_pkgname(configuration: Configuration) -> None:
"""
must generate correct pkgname property
"""
assert MirrorlistGenerator(configuration, "mirrorlist").pkgname == "aur-clone-mirrorlist"
configuration.set_option("mirrorlist", "package", "mirrorlist")
assert MirrorlistGenerator(configuration, "mirrorlist").pkgname == "mirrorlist"
def test_url(configuration: Configuration) -> None:
"""
must generate correct url property
"""
assert MirrorlistGenerator(configuration, "mirrorlist").url == ""
configuration.set_option("mirrorlist", "homepage", "homepage")
assert MirrorlistGenerator(configuration, "mirrorlist").url == "homepage"
def test_generate_mirrorlist(mirrorlist_generator: MirrorlistGenerator, mocker: MockerFixture) -> None:
"""
must correctly generate mirrorlist file
"""
write_mock = mocker.patch("pathlib.Path.write_text")
mirrorlist_generator._generate_mirrorlist(Path("local"))
write_mock.assert_called_once_with("Server = http://localhost\n", encoding="utf8")
def test_package(mirrorlist_generator: MirrorlistGenerator) -> None:
"""
must generate package function correctly
"""
assert mirrorlist_generator.package() == """{
install -Dm644 "$srcdir/mirrorlist" "$pkgdir/etc/pacman.d/aur-clone-mirrorlist"
}"""
def test_patches(mirrorlist_generator: MirrorlistGenerator) -> None:
"""
must generate additional patch list
"""
patches = {patch.key: patch for patch in mirrorlist_generator.patches()}
assert "backup" in patches
assert patches["backup"].value == [str(mirrorlist_generator.path)]
def test_sources(mirrorlist_generator: MirrorlistGenerator) -> None:
"""
must return valid sources files list
"""
assert mirrorlist_generator.sources().get("mirrorlist")

View File

@ -0,0 +1,141 @@
import datetime
import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call as MockCall
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
from ahriman.core.util import utcnow
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_license(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must return empty license list
"""
assert pkgbuild_generator.license == []
def test_pkgdesc(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must raise NotImplementedError on missing pkgdesc property
"""
with pytest.raises(NotImplementedError):
assert pkgbuild_generator.pkgdesc
def test_pkgname(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must raise NotImplementedError on missing pkgname property
"""
with pytest.raises(NotImplementedError):
assert pkgbuild_generator.pkgname
def test_pkgver(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
"""
must implement default version as current date
"""
mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.utcnow", return_value=datetime.datetime(2002, 3, 11))
assert pkgbuild_generator.pkgver == utcnow().strftime("20020311")
def test_url(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must return empty url
"""
assert pkgbuild_generator.url == ""
def test_install(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must return empty install function
"""
assert pkgbuild_generator.install() is None
def test_package(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must raise NotImplementedError on missing package function
"""
with pytest.raises(NotImplementedError):
pkgbuild_generator.package()
def test_patches(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must return empty patches list
"""
assert pkgbuild_generator.patches() == []
def test_sources(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must return empty sources list
"""
assert pkgbuild_generator.sources() == {}
def test_write_install(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
"""
must write install file
"""
mocker.patch.object(PkgbuildGenerator, "pkgname", "package")
mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.install", return_value="content")
write_mock = mocker.patch("pathlib.Path.write_text")
assert pkgbuild_generator.write_install(Path("local")) == [PkgbuildPatch("install", "package.install")]
write_mock.assert_called_once_with("content")
def test_write_install_empty(pkgbuild_generator: PkgbuildGenerator) -> None:
"""
must return empty patch list for missing install function
"""
assert pkgbuild_generator.write_install(Path("local")) == []
def test_write_pkgbuild(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
"""
must write PKGBUILD content to file
"""
path = Path("local")
for prop in ("pkgdesc", "pkgname"):
mocker.patch.object(PkgbuildGenerator, prop, "")
mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.package", return_value="{}")
patches_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.patches",
return_value=[PkgbuildPatch("property", "value")])
install_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_install",
return_value=[PkgbuildPatch("install", "pkgname.install")])
sources_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_sources",
return_value=[PkgbuildPatch("source", []), PkgbuildPatch("sha512sums", [])])
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
pkgbuild_generator.write_pkgbuild(path)
patches_mock.assert_called_once_with()
install_mock.assert_called_once_with(path)
sources_mock.assert_called_once_with(path)
write_mock.assert_has_calls([MockCall(path / "PKGBUILD")] * 12)
def test_write_sources(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
"""
must write sources files
"""
path = Path("local")
generator_mock = MagicMock()
sources_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.sources",
return_value={"source": generator_mock})
open_mock = mocker.patch("pathlib.Path.open")
hash_mock = MagicMock()
hash_mock.hexdigest.return_value = "hash"
mocker.patch("hashlib.sha512", return_value=hash_mock)
assert pkgbuild_generator.write_sources(path) == [
PkgbuildPatch("source", ["source"]),
PkgbuildPatch("sha512sums", ["hash"]),
]
generator_mock.assert_called_once_with(path / "source")
sources_mock.assert_called_once_with()
open_mock.assert_called_once_with("rb")

View File

@ -0,0 +1,30 @@
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG
from ahriman.core.support import KeyringTrigger
from ahriman.models.context_key import ContextKey
def test_configuration_sections(configuration: Configuration) -> None:
"""
must correctly parse target list
"""
configuration.set_option("keyring", "target", "a b c")
assert KeyringTrigger.configuration_sections(configuration) == ["a", "b", "c"]
configuration.remove_option("keyring", "target")
assert KeyringTrigger.configuration_sections(configuration) == []
def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run report for specified targets
"""
gpg_mock = mocker.patch("ahriman.core._Context.get")
run_mock = mocker.patch("ahriman.core.support.package_creator.PackageCreator.run")
trigger = KeyringTrigger("x86_64", configuration)
trigger.on_start()
gpg_mock.assert_called_once_with(ContextKey("sign", GPG))
run_mock.assert_called_once_with()

View File

@ -0,0 +1,26 @@
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.support import MirrorlistTrigger
def test_configuration_sections(configuration: Configuration) -> None:
"""
must correctly parse target list
"""
configuration.set_option("mirrorlist", "target", "a b c")
assert MirrorlistTrigger.configuration_sections(configuration) == ["a", "b", "c"]
configuration.remove_option("mirrorlist", "target")
assert MirrorlistTrigger.configuration_sections(configuration) == []
def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run report for specified targets
"""
run_mock = mocker.patch("ahriman.core.support.package_creator.PackageCreator.run")
trigger = MirrorlistTrigger("x86_64", configuration)
trigger.on_start()
run_mock.assert_called_once_with()

View File

@ -0,0 +1,40 @@
import pytest
from pytest_mock import MockerFixture
from ahriman.core.database import SQLite
from ahriman.core.support.package_creator import PackageCreator
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription
def test_run(package_creator: PackageCreator, database: SQLite, mocker: MockerFixture) -> None:
"""
must correctly process package creation
"""
package = Package(
base=package_creator.generator.pkgname,
version=package_creator.generator.pkgver,
remote=None,
packages={package_creator.generator.pkgname: PackageDescription()},
)
local_path = package_creator.configuration.repository_paths.cache_for(package_creator.generator.pkgname)
rmtree_mock = mocker.patch("shutil.rmtree")
database_mock = mocker.patch("ahriman.core._Context.get", return_value=database)
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
insert_mock = mocker.patch("ahriman.core.database.SQLite.package_update")
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
package_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package)
write_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_pkgbuild")
package_creator.run()
rmtree_mock.assert_called_once_with(local_path, ignore_errors=True)
mkdir_mock.assert_called_once_with(mode=0o755, parents=True, exist_ok=True)
write_mock.assert_called_once_with(local_path)
init_mock.assert_called_once_with(local_path)
package_mock.assert_called_once_with(local_path, "x86_64")
database_mock.assert_called_once_with(ContextKey("database", SQLite))
insert_mock.assert_called_once_with(package, pytest.helpers.anyvar(int))

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