mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
packagers support
This commit is contained in:
parent
d495163fdd
commit
30b108531a
@ -58,7 +58,7 @@ systemd-machine-id-setup &> /dev/null
|
||||
# otherwise we prepend executable by sudo command
|
||||
if [ -n "$AHRIMAN_FORCE_ROOT" ]; then
|
||||
AHRIMAN_EXECUTABLE=("ahriman")
|
||||
elif ahriman help-commands-unsafe --command="$*" &> /dev/null; then
|
||||
elif ahriman help-commands-unsafe -- "$@" &> /dev/null; then
|
||||
AHRIMAN_EXECUTABLE=("sudo" "-u" "$AHRIMAN_USER" "--" "ahriman")
|
||||
else
|
||||
AHRIMAN_EXECUTABLE=("ahriman")
|
||||
|
@ -1,4 +1,4 @@
|
||||
.TH AHRIMAN "1" "2023\-05\-28" "ahriman" "Generated Python Manual"
|
||||
.TH AHRIMAN "1" "2023\-06\-03" "ahriman" "Generated Python Manual"
|
||||
.SH NAME
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
@ -199,13 +199,12 @@ show help message for application or command and exit
|
||||
show help message for specific command
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR
|
||||
usage: ahriman help\-commands\-unsafe [\-h] [\-\-command COMMAND]
|
||||
usage: ahriman help\-commands\-unsafe [\-h] [command ...]
|
||||
|
||||
list unsafe commands as defined in default args
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman help\-commands\-unsafe'\/\fR
|
||||
.TP
|
||||
\fB\-\-command\fR \fI\,COMMAND\/\fR
|
||||
\fBcommand\fR
|
||||
instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1
|
||||
otherwise
|
||||
|
||||
@ -226,7 +225,7 @@ print application and its dependencies versions
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-add'\/\fR
|
||||
usage: ahriman package\-add [\-h] [\-\-dependencies | \-\-no\-dependencies] [\-e] [\-n] [\-y]
|
||||
[\-s {auto,archive,aur,directory,local,remote,repository}]
|
||||
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME]
|
||||
package [package ...]
|
||||
|
||||
add existing or new package to the build queue
|
||||
@ -256,6 +255,10 @@ download fresh package databases from the mirror before actions, \-yy to force r
|
||||
\fB\-s\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR, \fB\-\-source\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR
|
||||
explicitly specify the package source for this command
|
||||
|
||||
.TP
|
||||
\fB\-u\fR \fI\,USERNAME\/\fR, \fB\-\-username\fR \fI\,USERNAME\/\fR
|
||||
build as user
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR
|
||||
usage: ahriman package\-remove [\-h] package [package ...]
|
||||
|
||||
@ -457,7 +460,7 @@ download fresh package databases from the mirror before actions, \-yy to force r
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR
|
||||
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-e]
|
||||
[\-s {unknown,pending,building,failed,success}]
|
||||
[\-s {unknown,pending,building,failed,success}] [\-u USERNAME]
|
||||
|
||||
force rebuild whole repository
|
||||
|
||||
@ -484,6 +487,10 @@ return non\-zero exit status if result is empty
|
||||
\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
|
||||
|
||||
.TP
|
||||
\fB\-u\fR \fI\,USERNAME\/\fR, \fB\-\-username\fR \fI\,USERNAME\/\fR
|
||||
build as user
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR
|
||||
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
|
||||
|
||||
@ -553,7 +560,7 @@ instead of running all triggers as set by configuration, just process specified
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR
|
||||
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e]
|
||||
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-vcs | \-\-no\-vcs] [\-y]
|
||||
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
|
||||
[package ...]
|
||||
|
||||
check for packages updates and run build process if requested
|
||||
@ -587,6 +594,10 @@ enable or disable checking of local packages for updates
|
||||
\fB\-\-manual\fR, \fB\-\-no\-manual\fR
|
||||
include or exclude manual updates
|
||||
|
||||
.TP
|
||||
\fB\-u\fR \fI\,USERNAME\/\fR, \fB\-\-username\fR \fI\,USERNAME\/\fR
|
||||
build as user
|
||||
|
||||
.TP
|
||||
\fB\-\-vcs\fR, \fB\-\-no\-vcs\fR
|
||||
fetch actual version of VCS packages
|
||||
@ -724,7 +735,8 @@ drop into python shell while having created application
|
||||
instead of dropping into shell, just execute the specified code
|
||||
|
||||
.SH COMMAND \fI\,'ahriman user\-add'\/\fR
|
||||
usage: ahriman user\-add [\-h] [\-p PASSWORD] [\-r {unauthorized,read,reporter,full}] [\-s] username
|
||||
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-r {unauthorized,read,reporter,full}] [\-s]
|
||||
username
|
||||
|
||||
update user for web services with the given password and role. In case if password was not entered it will be asked interactively
|
||||
|
||||
@ -733,6 +745,14 @@ update user for web services with the given password and role. In case if passwo
|
||||
username for web service
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman user\-add'\/\fR
|
||||
.TP
|
||||
\fB\-\-key\fR \fI\,KEY\/\fR
|
||||
optional PGP key used by this user. The private key must be imported
|
||||
|
||||
.TP
|
||||
\fB\-\-packager\fR \fI\,PACKAGER\/\fR
|
||||
optional packager id used for build process in form of `Name Surname <mail@example.com>`
|
||||
|
||||
.TP
|
||||
\fB\-p\fR \fI\,PASSWORD\/\fR, \fB\-\-password\fR \fI\,PASSWORD\/\fR
|
||||
user password. Blank password will be treated as empty password, which is in particular must be used for OAuth2
|
||||
|
@ -68,6 +68,14 @@ ahriman.core.database.migrations.m007\_check\_depends module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.migrations.m008\_packagers module
|
||||
-------------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.migrations.m008_packagers
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -116,6 +116,14 @@ ahriman.models.package\_source module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.packagers module
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.packagers
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.pacman\_synchronization module
|
||||
---------------------------------------------
|
||||
|
||||
|
@ -6,13 +6,13 @@ _shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--confi
|
||||
_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_help_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help' '--command')
|
||||
_shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_help_updates_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_help_version_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_version_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_package_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source')
|
||||
_shtab_ahriman_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source')
|
||||
_shtab_ahriman_package_update_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source')
|
||||
_shtab_ahriman_package_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username')
|
||||
_shtab_ahriman_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username')
|
||||
_shtab_ahriman_package_update_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username')
|
||||
_shtab_ahriman_package_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_package_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status')
|
||||
@ -31,8 +31,8 @@ _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_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' '-s' '--status')
|
||||
_shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code' '-s' '--status')
|
||||
_shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code' '-s' '--status' '-u' '--username')
|
||||
_shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code' '-s' '--status' '-u' '--username')
|
||||
_shtab_ahriman_repo_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')
|
||||
@ -45,8 +45,8 @@ _shtab_ahriman_repo_sync_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_sync_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_tree_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_triggers_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_repo_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
@ -65,7 +65,7 @@ _shtab_ahriman_repo_setup_option_strings=('-h' '--help' '--build-as-user' '--bui
|
||||
_shtab_ahriman_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_service_shell_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_shell_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_user_add_option_strings=('-h' '--help' '-p' '--password' '-r' '--role' '-s' '--secure')
|
||||
_shtab_ahriman_user_add_option_strings=('-h' '--help' '--key' '--packager' '-p' '--password' '-r' '--role' '-s' '--secure')
|
||||
_shtab_ahriman_user_list_option_strings=('-h' '--help' '-e' '--exit-code' '-r' '--role')
|
||||
_shtab_ahriman_user_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_web_option_strings=('-h' '--help')
|
||||
@ -133,6 +133,7 @@ _shtab_ahriman_search___info_nargs=0
|
||||
_shtab_ahriman_search___no_info_nargs=0
|
||||
_shtab_ahriman_help__h_nargs=0
|
||||
_shtab_ahriman_help___help_nargs=0
|
||||
_shtab_ahriman_help_commands_unsafe_pos_0_nargs=*
|
||||
_shtab_ahriman_help_commands_unsafe__h_nargs=0
|
||||
_shtab_ahriman_help_commands_unsafe___help_nargs=0
|
||||
_shtab_ahriman_help_updates__h_nargs=0
|
||||
|
@ -95,6 +95,7 @@ _shtab_ahriman_add_options=(
|
||||
{-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]"
|
||||
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
|
||||
{-u,--username}"[build as user]:username:"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
@ -151,7 +152,7 @@ _shtab_ahriman_help_options=(
|
||||
|
||||
_shtab_ahriman_help_commands_unsafe_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--command[instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise]:command:"
|
||||
"(*)::instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_help_updates_options=(
|
||||
@ -192,6 +193,7 @@ _shtab_ahriman_package_add_options=(
|
||||
{-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]"
|
||||
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
|
||||
{-u,--username}"[build as user]:username:"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
@ -227,6 +229,7 @@ _shtab_ahriman_package_update_options=(
|
||||
{-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]"
|
||||
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
|
||||
{-u,--username}"[build as user]:username:"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
@ -263,6 +266,7 @@ _shtab_ahriman_rebuild_options=(
|
||||
"--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]"
|
||||
{-s,--status}"[filter packages by status. Requires --from-database to be set]:status:(unknown pending building failed success)"
|
||||
{-u,--username}"[build as user]:username:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_remove_options=(
|
||||
@ -349,6 +353,7 @@ _shtab_ahriman_repo_rebuild_options=(
|
||||
"--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]"
|
||||
{-s,--status}"[filter packages by status. Requires --from-database to be set]:status:(unknown pending building failed success)"
|
||||
{-u,--username}"[build as user]:username:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_remove_unknown_options=(
|
||||
@ -413,6 +418,7 @@ _shtab_ahriman_repo_update_options=(
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--local,--no-local}"[enable or disable checking of local packages for updates]:local:"
|
||||
{--manual,--no-manual}"[include or exclude manual updates]:manual:"
|
||||
{-u,--username}"[build as user]:username:"
|
||||
{--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]"
|
||||
"(*)::filter check by package base:"
|
||||
@ -529,6 +535,7 @@ _shtab_ahriman_update_options=(
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--local,--no-local}"[enable or disable checking of local packages for updates]:local:"
|
||||
{--manual,--no-manual}"[include or exclude manual updates]:manual:"
|
||||
{-u,--username}"[build as user]:username:"
|
||||
{--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]"
|
||||
"(*)::filter check by package base:"
|
||||
@ -536,6 +543,8 @@ _shtab_ahriman_update_options=(
|
||||
|
||||
_shtab_ahriman_user_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--key[optional PGP key used by this user. The private key must be imported]:key:"
|
||||
"--packager[optional packager id used for build process in form of \`Name Surname \<mail\@example.com\>\`]:packager:"
|
||||
{-p,--password}"[user password. Blank password will be treated as empty password, which is in particular must be used for OAuth2 authorization type.]:password:"
|
||||
{-r,--role}"[user access level]:role:(unauthorized read reporter full)"
|
||||
{-s,--secure}"[set file permissions to user-only]"
|
||||
|
@ -87,7 +87,6 @@ Settings for signing packages or repository. Group name can refer to architectur
|
||||
|
||||
* ``target`` - configuration flag to enable signing, space separated list of strings, required. Allowed values are ``package`` (sign each package separately), ``repository`` (sign repository database file).
|
||||
* ``key`` - default PGP key, string, required. This key will also be used for database signing if enabled.
|
||||
* ``key_*`` settings - PGP key which will be used for specific packages, string, optional. For example, if there is ``key_yay`` option the specified key will be used for yay package and default key for others.
|
||||
|
||||
``web:*`` groups
|
||||
----------------
|
||||
|
@ -64,7 +64,7 @@ Initial setup
|
||||
.. code-block:: shell
|
||||
|
||||
echo 'Cmnd_Alias CARCHBUILD_CMD = /usr/local/bin/ahriman-x86_64-build *' | tee -a /etc/sudoers.d/ahriman
|
||||
echo 'ahriman ALL=(ALL) NOPASSWD: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
|
||||
echo 'ahriman ALL=(ALL) NOPASSWD:SETENV: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
|
||||
chmod 400 /etc/sudoers.d/ahriman
|
||||
|
||||
This command supports several arguments, kindly refer to its help message.
|
||||
|
@ -87,6 +87,7 @@
|
||||
<th data-sortable="true" data-field="packages">packages</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="groups">groups</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="licenses">licenses</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="packager">packager</th>
|
||||
<th data-sortable="true" data-field="timestamp">last update</th>
|
||||
<th data-sortable="true" data-cell-style="statusFormat" data-field="status">status</th>
|
||||
</tr>
|
||||
|
@ -98,6 +98,7 @@
|
||||
id: package_base,
|
||||
base: web_url ? `<a href="${safe(web_url)}" title="${safe(package_base)}">${safe(package_base)}</a>` : safe(package_base),
|
||||
version: safe(description.package.version),
|
||||
packager: description.package.packager ? safe(description.package.packager) : "",
|
||||
packages: listToTable(Object.keys(description.package.packages)),
|
||||
groups: listToTable(extractListProperties(description.package, "groups")),
|
||||
licenses: listToTable(extractListProperties(description.package, "licenses")),
|
||||
@ -120,8 +121,8 @@
|
||||
table.bootstrapTable("hideLoading");
|
||||
} else {
|
||||
// other errors
|
||||
const messaga = error => { return `Could not load list of packages: ${error}`; };
|
||||
showFailure("Load failure", messaga, jqXHR, errorThrown);
|
||||
const message = error => { return `Could not load list of packages: ${error}`; };
|
||||
showFailure("Load failure", message, jqXHR, errorThrown);
|
||||
}
|
||||
hideControls(true);
|
||||
},
|
||||
|
@ -27,7 +27,7 @@ from typing import TypeVar
|
||||
|
||||
from ahriman import version
|
||||
from ahriman.application import handlers
|
||||
from ahriman.core.util import enum_values
|
||||
from ahriman.core.util import enum_values, extract_user
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
@ -187,8 +187,8 @@ def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.Argument
|
||||
"""
|
||||
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
|
||||
description="list unsafe commands as defined in default args", formatter_class=_formatter)
|
||||
parser.add_argument("--command", help="instead of showing commands, just test command line for unsafe subcommand "
|
||||
"and return 0 in case if command is safe and 1 otherwise")
|
||||
parser.add_argument("command", help="instead of showing commands, just test command line for unsafe subcommand "
|
||||
"and return 0 in case if command is safe and 1 otherwise", nargs="*")
|
||||
parser.set_defaults(handler=handlers.UnsafeCommands, architecture=[""], lock=None, report=False, quiet=True,
|
||||
unsafe=True, parser=_parser)
|
||||
return parser
|
||||
@ -262,6 +262,7 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
action="count", default=False)
|
||||
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
||||
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
|
||||
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
parser.set_defaults(handler=handlers.Add)
|
||||
return parser
|
||||
|
||||
@ -481,7 +482,8 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=False)
|
||||
parser.set_defaults(handler=handlers.Update, dependencies=False, dry_run=True, aur=True, local=True, manual=False)
|
||||
parser.set_defaults(handler=handlers.Update, dependencies=False, dry_run=True, aur=True, local=True, manual=False,
|
||||
username=None)
|
||||
return parser
|
||||
|
||||
|
||||
@ -578,6 +580,7 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
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.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
parser.set_defaults(handler=handlers.Rebuild)
|
||||
return parser
|
||||
|
||||
@ -752,6 +755,7 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--manual", help="include or exclude manual updates",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
parser.add_argument("--vcs", help="fetch actual version of VCS packages",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
@ -923,6 +927,9 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"root privileges because it performs write to filesystem configuration.",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("username", help="username for web service")
|
||||
parser.add_argument("--key", help="optional PGP key used by this user. The private key must be imported")
|
||||
parser.add_argument("--packager", help="optional packager id used for build process in form of "
|
||||
"`Name Surname <mail@example.com>`")
|
||||
parser.add_argument("-p", "--password", help="user password. Blank password will be treated as empty password, "
|
||||
"which is in particular must be used for OAuth2 authorization type.")
|
||||
parser.add_argument("-r", "--role", help="user access level",
|
||||
@ -949,8 +956,8 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("username", help="filter users by username", nargs="?")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("-r", "--role", help="filter users by role", type=UserAccess, choices=enum_values(UserAccess))
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, report=False, # nosec
|
||||
password="", quiet=True, unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, report=False,
|
||||
quiet=True, unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -968,8 +975,8 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="remove user from the user mapping and update the configuration",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("username", help="username for web service")
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, report=False, # nosec
|
||||
password="", quiet=True)
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, report=False,
|
||||
quiet=True)
|
||||
return parser
|
||||
|
||||
|
||||
|
@ -39,7 +39,7 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
>>> configuration = Configuration()
|
||||
>>> application = Application("x86_64", configuration, report=True, unsafe=False)
|
||||
>>> # add packages to build queue
|
||||
>>> application.add(["ahriman"], PackageSource.AUR, without_dependencies=False)
|
||||
>>> application.add(["ahriman"], PackageSource.AUR)
|
||||
>>>
|
||||
>>> # check for updates
|
||||
>>> updates = application.updates([], aur=True, local=True, manual=True, vcs=True, log_fn=print)
|
||||
@ -96,21 +96,25 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
Args:
|
||||
packages(list[Package]): list of source packages of which dependencies have to be processed
|
||||
process_dependencies(bool): if no set, dependencies will not be processed
|
||||
|
||||
Returns:
|
||||
list[Package]: updated packages list. Packager for dependencies will be copied from
|
||||
original package
|
||||
"""
|
||||
def missing_dependencies(source: Iterable[Package]) -> set[str]:
|
||||
# build initial list of dependencies
|
||||
result = set()
|
||||
for package in source:
|
||||
result.update(package.depends_build)
|
||||
def missing_dependencies(source: Iterable[Package]) -> dict[str, str | None]:
|
||||
# append list of known packages with packages which are in current sources
|
||||
satisfied_packages = known_packages | {
|
||||
single
|
||||
for package in source
|
||||
for single in package.packages_full
|
||||
}
|
||||
|
||||
# remove ones which are already well-known
|
||||
result = result.difference(known_packages)
|
||||
|
||||
# remove ones which are in this list already
|
||||
for package in source:
|
||||
result = result.difference(package.packages_full)
|
||||
|
||||
return result
|
||||
return {
|
||||
dependency: package.packager
|
||||
for package in source
|
||||
for dependency in package.depends_build
|
||||
if dependency not in satisfied_packages
|
||||
}
|
||||
|
||||
if not process_dependencies or not packages:
|
||||
return packages
|
||||
@ -119,8 +123,8 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
with_dependencies = {package.base: package for package in packages}
|
||||
|
||||
while missing := missing_dependencies(with_dependencies.values()):
|
||||
for package_name in missing:
|
||||
package = Package.from_aur(package_name, self.repository.pacman)
|
||||
for package_name, username in missing.items():
|
||||
package = Package.from_aur(package_name, self.repository.pacman, username)
|
||||
with_dependencies[package.base] = package
|
||||
|
||||
return list(with_dependencies.values())
|
||||
|
@ -55,15 +55,15 @@ class ApplicationPackages(ApplicationProperties):
|
||||
dst = self.repository.paths.packages / local_path.name
|
||||
shutil.copy(local_path, dst)
|
||||
|
||||
def _add_aur(self, source: str) -> None:
|
||||
def _add_aur(self, source: str, username: str | None) -> None:
|
||||
"""
|
||||
add package from AUR
|
||||
|
||||
Args:
|
||||
source(str): package base name
|
||||
username(str | None): optional override of username for build process
|
||||
"""
|
||||
package = Package.from_aur(source, self.repository.pacman)
|
||||
|
||||
package = Package.from_aur(source, self.repository.pacman, username)
|
||||
self.database.build_queue_insert(package)
|
||||
self.database.remote_update(package)
|
||||
|
||||
@ -81,23 +81,24 @@ class ApplicationPackages(ApplicationProperties):
|
||||
for full_path in filter(package_like, local_dir.iterdir()):
|
||||
self._add_archive(str(full_path))
|
||||
|
||||
def _add_local(self, source: str) -> None:
|
||||
def _add_local(self, source: str, username: str | None) -> None:
|
||||
"""
|
||||
add package from local PKGBUILDs
|
||||
|
||||
Args:
|
||||
source(str): path to directory with local source files
|
||||
username(str | None): optional override of username for build process
|
||||
|
||||
Raises:
|
||||
UnknownPackageError: if specified package is unknown or doesn't exist
|
||||
"""
|
||||
if (source_dir := Path(source)).is_dir():
|
||||
package = Package.from_build(source_dir, self.architecture)
|
||||
package = Package.from_build(source_dir, self.architecture, username)
|
||||
cache_dir = self.repository.paths.cache_for(package.base)
|
||||
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
|
||||
elif (source_dir := self.repository.paths.cache_for(source)).is_dir():
|
||||
package = Package.from_build(source_dir, self.architecture)
|
||||
package = Package.from_build(source_dir, self.architecture, username)
|
||||
else:
|
||||
raise UnknownPackageError(source)
|
||||
|
||||
@ -122,29 +123,31 @@ class ApplicationPackages(ApplicationProperties):
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
local_file.write(chunk)
|
||||
|
||||
def _add_repository(self, source: str, *_: Any) -> None:
|
||||
def _add_repository(self, source: str, username: str | None) -> None:
|
||||
"""
|
||||
add package from official repository
|
||||
|
||||
Args:
|
||||
source(str): package base name
|
||||
username(str | None): optional override of username for build process
|
||||
"""
|
||||
package = Package.from_official(source, self.repository.pacman)
|
||||
package = Package.from_official(source, self.repository.pacman, username)
|
||||
self.database.build_queue_insert(package)
|
||||
self.database.remote_update(package)
|
||||
|
||||
def add(self, names: Iterable[str], source: PackageSource) -> None:
|
||||
def add(self, names: Iterable[str], source: PackageSource, username: str | None = None) -> None:
|
||||
"""
|
||||
add packages for the next build
|
||||
|
||||
Args:
|
||||
names(Iterable[str]): list of package bases to add
|
||||
source(PackageSource): package source to add
|
||||
username(str | None, optional): optional override of username for build process (Default value = None)
|
||||
"""
|
||||
for name in names:
|
||||
resolved_source = source.resolve(name)
|
||||
fn = getattr(self, f"_add_{resolved_source.value}")
|
||||
fn(name)
|
||||
fn(name, username)
|
||||
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
|
@ -25,6 +25,7 @@ from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.formatters import UpdatePrinter
|
||||
from ahriman.core.tree import Tree
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
@ -83,7 +84,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
if archive.filepath is None:
|
||||
self.logger.warning("filepath is empty for %s", package.base)
|
||||
continue # avoid mypy warning
|
||||
self.repository.sign.process_sign_package(archive.filepath, package.base)
|
||||
self.repository.sign.process_sign_package(archive.filepath, None)
|
||||
# sign repository database if set
|
||||
self.repository.sign.process_sign_repository(self.repository.repo.repo_path)
|
||||
# process triggers
|
||||
@ -104,14 +105,14 @@ class ApplicationRepository(ApplicationProperties):
|
||||
packages: list[str] = []
|
||||
for single in probe.packages:
|
||||
try:
|
||||
_ = Package.from_aur(single, self.repository.pacman)
|
||||
_ = Package.from_aur(single, self.repository.pacman, None)
|
||||
except Exception:
|
||||
packages.append(single)
|
||||
return packages
|
||||
|
||||
def unknown_local(probe: Package) -> list[str]:
|
||||
cache_dir = self.repository.paths.cache_for(probe.base)
|
||||
local = Package.from_build(cache_dir, self.architecture)
|
||||
local = Package.from_build(cache_dir, self.architecture, None)
|
||||
packages = set(probe.packages.keys()).difference(local.packages.keys())
|
||||
return list(packages)
|
||||
|
||||
@ -123,12 +124,14 @@ class ApplicationRepository(ApplicationProperties):
|
||||
result.extend(unknown_aur(package)) # local package not found
|
||||
return result
|
||||
|
||||
def update(self, updates: Iterable[Package]) -> Result:
|
||||
def update(self, updates: Iterable[Package], packagers: Packagers | None = None) -> Result:
|
||||
"""
|
||||
run package updates
|
||||
|
||||
Args:
|
||||
updates(Iterable[Package]): list of packages to update
|
||||
packagers(Packagers | None, optional): optional override of username for build process
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
Result: update result
|
||||
@ -136,7 +139,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
def process_update(paths: Iterable[Path], result: Result) -> None:
|
||||
if not paths:
|
||||
return # don't need to process if no update supplied
|
||||
update_result = self.repository.process_update(paths)
|
||||
update_result = self.repository.process_update(paths, packagers)
|
||||
self.on_result(result.merge(update_result))
|
||||
|
||||
# process built packages
|
||||
@ -148,7 +151,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
tree = Tree.resolve(updates)
|
||||
for num, level in enumerate(tree):
|
||||
self.logger.info("processing level #%i %s", num, [package.base for package in level])
|
||||
build_result = self.repository.process_build(level)
|
||||
build_result = self.repository.process_build(level, packagers)
|
||||
packages = self.repository.packages_built()
|
||||
process_update(packages, build_result)
|
||||
|
||||
|
@ -22,6 +22,7 @@ import argparse
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.packagers import Packagers
|
||||
|
||||
|
||||
class Add(Handler):
|
||||
@ -45,12 +46,14 @@ class Add(Handler):
|
||||
application = Application(architecture, configuration,
|
||||
report=report, unsafe=unsafe, refresh_pacman_database=args.refresh)
|
||||
application.on_start()
|
||||
application.add(args.package, args.source)
|
||||
application.add(args.package, args.source, args.username)
|
||||
if not args.now:
|
||||
return
|
||||
|
||||
packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False,
|
||||
log_fn=application.logger.info)
|
||||
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
||||
result = application.update(packages)
|
||||
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
|
||||
|
||||
result = application.update(packages, packagers)
|
||||
Add.check_if_empty(args.exit_code, result.is_empty)
|
||||
|
@ -78,7 +78,7 @@ class Patch(Handler):
|
||||
tuple[str, PkgbuildPatch]: package base and created PKGBUILD patch based on the diff from master HEAD
|
||||
to current files
|
||||
"""
|
||||
package = Package.from_build(sources_dir, architecture)
|
||||
package = Package.from_build(sources_dir, architecture, None)
|
||||
patch = Sources.patch_create(sources_dir, *track)
|
||||
return package.base, PkgbuildPatch(None, patch)
|
||||
|
||||
|
@ -57,7 +57,7 @@ class Rebuild(Handler):
|
||||
UpdatePrinter(package, package.version).print(verbose=True)
|
||||
return
|
||||
|
||||
result = application.update(updates)
|
||||
result = application.update(updates, args.username)
|
||||
Rebuild.check_if_empty(args.exit_code, result.is_empty)
|
||||
|
||||
@staticmethod
|
||||
|
@ -49,7 +49,7 @@ class ServiceUpdates(Handler):
|
||||
"""
|
||||
application = Application(architecture, configuration, report=report, unsafe=unsafe)
|
||||
|
||||
remote = Package.from_aur("ahriman", application.repository.pacman)
|
||||
remote = Package.from_aur("ahriman", application.repository.pacman, None)
|
||||
release = remote.version.rsplit("-", 1)[-1] # we don't store pkgrel locally, so we just append it
|
||||
local_version = f"{version.__version__}-{release}"
|
||||
|
||||
|
@ -213,7 +213,7 @@ class Setup(Handler):
|
||||
"""
|
||||
command = Setup.build_command(paths.root, prefix, architecture)
|
||||
sudoers_file = Setup.build_command(Setup.SUDOERS_DIR_PATH, prefix, architecture)
|
||||
sudoers_file.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n", encoding="utf8")
|
||||
sudoers_file.write_text(f"ahriman ALL=(ALL) NOPASSWD:SETENV: {command} *\n", encoding="utf8")
|
||||
sudoers_file.chmod(0o400) # security!
|
||||
|
||||
@staticmethod
|
||||
|
@ -18,7 +18,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import shlex
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -47,14 +46,14 @@ class UnsafeCommands(Handler):
|
||||
"""
|
||||
parser = args.parser()
|
||||
unsafe_commands = UnsafeCommands.get_unsafe_commands(parser)
|
||||
if args.command is None:
|
||||
if args.command:
|
||||
UnsafeCommands.check_unsafe(args.command, unsafe_commands, parser)
|
||||
else:
|
||||
for command in unsafe_commands:
|
||||
StringPrinter(command).print(verbose=True)
|
||||
else:
|
||||
UnsafeCommands.check_unsafe(args.command, unsafe_commands, parser)
|
||||
|
||||
@staticmethod
|
||||
def check_unsafe(command: str, unsafe_commands: list[str], parser: argparse.ArgumentParser) -> None:
|
||||
def check_unsafe(command: list[str], unsafe_commands: list[str], parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
check if command is unsafe
|
||||
|
||||
@ -63,7 +62,7 @@ class UnsafeCommands(Handler):
|
||||
unsafe_commands(list[str]): list of unsafe commands
|
||||
parser(argparse.ArgumentParser): generated argument parser
|
||||
"""
|
||||
args = parser.parse_args(shlex.split(command))
|
||||
args = parser.parse_args(command)
|
||||
UnsafeCommands.check_if_empty(True, args.command in unsafe_commands)
|
||||
|
||||
@staticmethod
|
||||
|
@ -24,6 +24,7 @@ from collections.abc import Callable
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.packagers import Packagers
|
||||
|
||||
|
||||
class Update(Handler):
|
||||
@ -54,7 +55,9 @@ class Update(Handler):
|
||||
return
|
||||
|
||||
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
||||
result = application.update(packages)
|
||||
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
|
||||
|
||||
result = application.update(packages, packagers)
|
||||
Update.check_if_empty(args.exit_code, result.is_empty)
|
||||
|
||||
@staticmethod
|
||||
|
@ -156,4 +156,5 @@ class Users(Handler):
|
||||
if password is None:
|
||||
password = read_password()
|
||||
|
||||
return User(username=args.username, password=password, access=args.role)
|
||||
return User(username=args.username, password=password, access=args.role,
|
||||
packager_id=args.packager, key=args.key)
|
||||
|
@ -59,12 +59,13 @@ class Task(LazyLogging):
|
||||
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
|
||||
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
|
||||
|
||||
def build(self, sources_dir: Path) -> list[Path]:
|
||||
def build(self, sources_dir: Path, packager: str | None = None) -> list[Path]:
|
||||
"""
|
||||
run package build
|
||||
|
||||
Args:
|
||||
sources_dir(Path): path to where sources are
|
||||
packager(str | None, optional): optional packager override (Default value = None)
|
||||
|
||||
Returns:
|
||||
list[Path]: paths of produced packages
|
||||
@ -75,12 +76,18 @@ class Task(LazyLogging):
|
||||
command.extend(["--"] + self.makepkg_flags)
|
||||
self.logger.info("using %s for %s", command, self.package.base)
|
||||
|
||||
environment: dict[str, str] = {}
|
||||
if packager is not None:
|
||||
environment["PACKAGER"] = packager
|
||||
self.logger.info("using environment variables %s", environment)
|
||||
|
||||
Task._check_output(
|
||||
*command,
|
||||
exception=BuildError(self.package.base),
|
||||
cwd=sources_dir,
|
||||
logger=self.logger,
|
||||
user=self.uid)
|
||||
user=self.uid,
|
||||
environment=environment)
|
||||
|
||||
# well it is not actually correct, but we can deal with it
|
||||
packages = Task._check_output(
|
||||
|
@ -191,10 +191,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"sign": {
|
||||
"type": "dict",
|
||||
"allow_unknown": True,
|
||||
"keysrules": {
|
||||
"type": "string",
|
||||
"anyof_regex": ["^target$", "^key$", "^key_.*"],
|
||||
},
|
||||
"schema": {
|
||||
"target": {
|
||||
"type": "list",
|
||||
|
85
src/ahriman/core/database/migrations/m008_packagers.py
Normal file
85
src/ahriman/core/database/migrations/m008_packagers.py
Normal file
@ -0,0 +1,85 @@
|
||||
#
|
||||
# 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 users add column packager_id
|
||||
""",
|
||||
"""
|
||||
alter table users add column key_id
|
||||
""",
|
||||
"""
|
||||
alter table package_bases add column packager
|
||||
""",
|
||||
]
|
||||
|
||||
|
||||
def migrate_data(connection: Connection, configuration: Configuration) -> None:
|
||||
"""
|
||||
perform data migration
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
migrate_package_base_packager(connection, configuration)
|
||||
|
||||
|
||||
def migrate_package_base_packager(connection: Connection, configuration: Configuration) -> None:
|
||||
"""
|
||||
migrate package packager field
|
||||
|
||||
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()):
|
||||
package = Package.from_archive(full_path, pacman, remote=None)
|
||||
package_list.append({
|
||||
"package_base": package.base,
|
||||
"packager": package.packager,
|
||||
})
|
||||
|
||||
connection.executemany(
|
||||
"""
|
||||
update package_bases set
|
||||
packager = :packager
|
||||
where package_base = :package_base
|
||||
""",
|
||||
package_list
|
||||
)
|
@ -57,8 +57,9 @@ class AuthOperations(Operations):
|
||||
|
||||
def run(connection: Connection) -> list[User]:
|
||||
return [
|
||||
User(username=cursor["username"], password=cursor["password"], access=UserAccess(cursor["access"]))
|
||||
for cursor in connection.execute(
|
||||
User(username=row["username"], password=row["password"], access=UserAccess(row["access"]),
|
||||
packager_id=row["packager_id"], key=row["key_id"])
|
||||
for row in connection.execute(
|
||||
"""
|
||||
select * from users
|
||||
where (:username is null or username = :username) and (:access is null or access = :access)
|
||||
@ -91,12 +92,13 @@ class AuthOperations(Operations):
|
||||
connection.execute(
|
||||
"""
|
||||
insert into users
|
||||
(username, access, password)
|
||||
(username, access, password, packager_id, key_id)
|
||||
values
|
||||
(:username, :access, :password)
|
||||
(:username, :access, :password, :packager_id, :key_id)
|
||||
on conflict (username) do update set
|
||||
access = :access, password = :password
|
||||
access = :access, password = :password, packager_id = :packager_id, key_id = :key_id
|
||||
""",
|
||||
{"username": user.username.lower(), "access": user.access.value, "password": user.password})
|
||||
{"username": user.username.lower(), "access": user.access.value, "password": user.password,
|
||||
"packager_id": user.packager_id, "key_id": user.key})
|
||||
|
||||
self.with_connection(run, commit=True)
|
||||
|
@ -76,11 +76,12 @@ class PackageOperations(Operations):
|
||||
connection.execute(
|
||||
"""
|
||||
insert into package_bases
|
||||
(package_base, version, source, branch, git_url, path, web_url)
|
||||
(package_base, version, source, branch, git_url, path, web_url, packager)
|
||||
values
|
||||
(:package_base, :version, :source, :branch, :git_url, :path, :web_url)
|
||||
(:package_base, :version, :source, :branch, :git_url, :path, :web_url, :packager)
|
||||
on conflict (package_base) do update set
|
||||
version = :version, branch = :branch, git_url = :git_url, path = :path, web_url = :web_url, source = :source
|
||||
version = :version, branch = :branch, git_url = :git_url, path = :path, web_url = :web_url,
|
||||
source = :source, packager = :packager
|
||||
""",
|
||||
{
|
||||
"package_base": package.base,
|
||||
@ -90,6 +91,7 @@ class PackageOperations(Operations):
|
||||
"path": package.remote.path if package.remote is not None else None,
|
||||
"web_url": package.remote.web_url if package.remote is not None else None,
|
||||
"source": package.remote.source.value if package.remote is not None else None,
|
||||
"packager": package.packager,
|
||||
}
|
||||
)
|
||||
|
||||
@ -163,8 +165,9 @@ class PackageOperations(Operations):
|
||||
base=row["package_base"],
|
||||
version=row["version"],
|
||||
remote=RemoteSource.from_json(row),
|
||||
packages={})
|
||||
for row in connection.execute("""select * from package_bases""")
|
||||
packages={},
|
||||
packager=row["packager"] or None,
|
||||
) for row in connection.execute("""select * from package_bases""")
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
@ -77,8 +77,8 @@ class PatchOperations(Operations):
|
||||
"""
|
||||
def run(connection: Connection) -> list[tuple[str, PkgbuildPatch]]:
|
||||
return [
|
||||
(cursor["package_base"], PkgbuildPatch(cursor["variable"], cursor["patch"]))
|
||||
for cursor in connection.execute(
|
||||
(row["package_base"], PkgbuildPatch(row["variable"], row["patch"]))
|
||||
for row in connection.execute(
|
||||
"""select * from patches where :package_base is null or package_base = :package_base""",
|
||||
{"package_base": package_base})
|
||||
]
|
||||
|
@ -44,13 +44,13 @@ class RemotePush(LazyLogging):
|
||||
remote_source(RemoteSource): repository remote source (remote pull url and branch)
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: Configuration, database: SQLite, section: str) -> None:
|
||||
def __init__(self, database: SQLite, configuration: Configuration, section: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
database(SQLite): database instance
|
||||
configuration(Configuration): configuration instance
|
||||
section(str): settings section name
|
||||
"""
|
||||
self.database = database
|
||||
|
@ -105,5 +105,5 @@ class RemotePushTrigger(Trigger):
|
||||
for target in self.targets:
|
||||
section, _ = self.configuration.gettype(
|
||||
target, self.architecture, fallback=self.CONFIGURATION_SCHEMA_FALLBACK)
|
||||
runner = RemotePush(self.configuration, database, section)
|
||||
runner = RemotePush(database, self.configuration, section)
|
||||
runner.run(result)
|
||||
|
@ -28,6 +28,7 @@ from ahriman.core.repository.cleaner import Cleaner
|
||||
from ahriman.core.util import safe_filename
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
@ -63,30 +64,35 @@ class Executor(Cleaner):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def process_build(self, updates: Iterable[Package]) -> Result:
|
||||
def process_build(self, updates: Iterable[Package], packagers: Packagers | None = None) -> Result:
|
||||
"""
|
||||
build packages
|
||||
|
||||
Args:
|
||||
updates(Iterable[Package]): list of packages properties to build
|
||||
packagers(Packagers | None, optional): optional override of username for build process
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
Result: build result
|
||||
"""
|
||||
def build_single(package: Package, local_path: Path) -> None:
|
||||
def build_single(package: Package, local_path: Path, packager_id: str | None) -> None:
|
||||
self.reporter.set_building(package.base)
|
||||
task = Task(package, self.configuration, self.paths)
|
||||
task.init(local_path, self.database)
|
||||
built = task.build(local_path)
|
||||
built = task.build(local_path, packager_id)
|
||||
for src in built:
|
||||
dst = self.paths.packages / src.name
|
||||
shutil.move(src, dst)
|
||||
|
||||
packagers = packagers or Packagers()
|
||||
|
||||
result = Result()
|
||||
for single in updates:
|
||||
with self.in_package_context(single.base), TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
|
||||
try:
|
||||
build_single(single, Path(dir_name))
|
||||
packager = self.packager(packagers, single.base)
|
||||
build_single(single, Path(dir_name), packager.packager_id)
|
||||
result.add_success(single)
|
||||
except Exception:
|
||||
self.reporter.set_failed(single.base)
|
||||
@ -158,12 +164,14 @@ class Executor(Cleaner):
|
||||
|
||||
return self.repo.repo_path
|
||||
|
||||
def process_update(self, packages: Iterable[Path]) -> Result:
|
||||
def process_update(self, packages: Iterable[Path], packagers: Packagers | None = None) -> Result:
|
||||
"""
|
||||
sign packages, add them to repository and update repository database
|
||||
|
||||
Args:
|
||||
packages(Iterable[Path]): list of filenames to run
|
||||
packagers(Packagers | None, optional): optional override of username for build process
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
Result: path to repository database
|
||||
@ -176,13 +184,13 @@ class Executor(Cleaner):
|
||||
shutil.move(self.paths.packages / archive.filename, self.paths.packages / safe)
|
||||
archive.filename = safe
|
||||
|
||||
def update_single(name: str | None, package_base: str) -> None:
|
||||
def update_single(name: str | None, package_base: str, packager_key: str | None) -> None:
|
||||
if name is None:
|
||||
self.logger.warning("received empty package name for base %s", package_base)
|
||||
return # suppress type checking, it never can be none actually
|
||||
# in theory, it might be NOT packages directory, but we suppose it is
|
||||
full_path = self.paths.packages / name
|
||||
files = self.sign.process_sign_package(full_path, package_base)
|
||||
files = self.sign.process_sign_package(full_path, packager_key)
|
||||
for src in files:
|
||||
dst = self.paths.repository / safe_filename(src.name)
|
||||
shutil.move(src, dst)
|
||||
@ -192,14 +200,17 @@ class Executor(Cleaner):
|
||||
current_packages = self.packages()
|
||||
removed_packages: list[str] = [] # list of packages which have been removed from the base
|
||||
updates = self.load_archives(packages)
|
||||
packagers = packagers or Packagers()
|
||||
|
||||
result = Result()
|
||||
for local in updates:
|
||||
with self.in_package_context(local.base):
|
||||
try:
|
||||
packager = self.packager(packagers, local.base)
|
||||
|
||||
for description in local.packages.values():
|
||||
rename(description, local.base)
|
||||
update_single(description.filename, local.base)
|
||||
update_single(description.filename, local.base, packager.key)
|
||||
self.reporter.set_success(local)
|
||||
result.add_success(local)
|
||||
|
||||
|
@ -27,8 +27,11 @@ from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.status.client import Client
|
||||
from ahriman.core.triggers import TriggerLoader
|
||||
from ahriman.core.util import check_user
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
from ahriman.models.user import User
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
class RepositoryProperties(LazyLogging):
|
||||
@ -83,3 +86,23 @@ class RepositoryProperties(LazyLogging):
|
||||
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
|
||||
self.reporter = Client.load(configuration, report=report)
|
||||
self.triggers = TriggerLoader.load(architecture, configuration)
|
||||
|
||||
def packager(self, packagers: Packagers, package_base: str) -> User:
|
||||
"""
|
||||
extract packager from configuration having username
|
||||
|
||||
Args:
|
||||
packagers(Packagers): packagers override holder
|
||||
package_base(str): package base to lookup
|
||||
|
||||
Returns:
|
||||
User | None: user found in database if any and empty object otherwise
|
||||
"""
|
||||
username = packagers.for_base(package_base)
|
||||
if username is None: # none to search
|
||||
return User(username="", password="", access=UserAccess.Read, packager_id=None, key=None) # nosec
|
||||
|
||||
if (user := self.database.user_get(username)) is not None: # found user
|
||||
return user
|
||||
# empty user with the username
|
||||
return User(username=username, password="", access=UserAccess.Read, packager_id=None, key=None) # nosec
|
||||
|
@ -65,9 +65,9 @@ class UpdateHandler(Cleaner):
|
||||
|
||||
try:
|
||||
if source == PackageSource.Repository:
|
||||
remote = Package.from_official(local.base, self.pacman)
|
||||
remote = Package.from_official(local.base, self.pacman, None)
|
||||
else:
|
||||
remote = Package.from_aur(local.base, self.pacman)
|
||||
remote = Package.from_aur(local.base, self.pacman, None)
|
||||
|
||||
if local.is_outdated(
|
||||
remote, self.paths,
|
||||
@ -98,7 +98,7 @@ class UpdateHandler(Cleaner):
|
||||
with self.in_package_context(cache_dir.name):
|
||||
try:
|
||||
Sources.fetch(cache_dir, remote=None)
|
||||
remote = Package.from_build(cache_dir, self.architecture)
|
||||
remote = Package.from_build(cache_dir, self.architecture, None)
|
||||
|
||||
local = packages.get(remote.base)
|
||||
if local is None:
|
||||
|
@ -19,7 +19,6 @@
|
||||
#
|
||||
import requests
|
||||
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -165,21 +164,6 @@ class GPG(LazyLogging):
|
||||
key_body = self.key_download(server, key)
|
||||
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]:
|
||||
"""
|
||||
gpg command wrapper
|
||||
@ -197,20 +181,21 @@ class GPG(LazyLogging):
|
||||
logger=self.logger)
|
||||
return [path, path.parent / f"{path.name}.sig"]
|
||||
|
||||
def process_sign_package(self, path: Path, package_base: str) -> list[Path]:
|
||||
def process_sign_package(self, path: Path, packager_key: str | None) -> list[Path]:
|
||||
"""
|
||||
sign package if required by configuration
|
||||
|
||||
Args:
|
||||
path(Path): path to file to sign
|
||||
package_base(str): package base required to check for key overrides
|
||||
packager_key(str | None): optional packager key to sign
|
||||
|
||||
Returns:
|
||||
list[Path]: list of generated files including original file
|
||||
"""
|
||||
if SignSettings.Packages not in self.targets:
|
||||
return [path]
|
||||
key = self.configuration.get("sign", f"key_{package_base}", fallback=self.default_key)
|
||||
|
||||
key = packager_key or self.default_key
|
||||
if key is None:
|
||||
self.logger.error("no default key set, skip package %s sign", path)
|
||||
return [path]
|
||||
|
@ -78,7 +78,7 @@ class Spawn(Thread, LazyLogging):
|
||||
result = callback(args, architecture)
|
||||
queue.put((process_id, result))
|
||||
|
||||
def _spawn_process(self, command: str, *args: str, **kwargs: str) -> None:
|
||||
def _spawn_process(self, command: str, *args: str, **kwargs: str | None) -> None:
|
||||
"""
|
||||
spawn external ahriman process with supplied arguments
|
||||
|
||||
@ -94,6 +94,8 @@ class Spawn(Thread, LazyLogging):
|
||||
arguments.extend(args)
|
||||
# named command arguments
|
||||
for argument, value in kwargs.items():
|
||||
if value is None:
|
||||
continue # skip null values
|
||||
arguments.append(f"--{argument}")
|
||||
if value:
|
||||
arguments.append(value)
|
||||
@ -122,27 +124,31 @@ class Spawn(Thread, LazyLogging):
|
||||
kwargs = {} if server is None else {"key-server": server}
|
||||
self._spawn_process("service-key-import", key, **kwargs)
|
||||
|
||||
def packages_add(self, packages: Iterable[str], *, now: bool) -> None:
|
||||
def packages_add(self, packages: Iterable[str], username: str | None, *, now: bool) -> None:
|
||||
"""
|
||||
add packages
|
||||
|
||||
Args:
|
||||
packages(Iterable[str]): packages list to add
|
||||
username(str | None): optional override of username for build process
|
||||
now(bool): build packages now
|
||||
"""
|
||||
kwargs = {"source": PackageSource.AUR.value} # avoid abusing by building non-aur packages
|
||||
# avoid abusing by building non-aur packages
|
||||
kwargs = {"source": PackageSource.AUR.value, "username": username}
|
||||
if now:
|
||||
kwargs["now"] = ""
|
||||
self._spawn_process("package-add", *packages, **kwargs)
|
||||
|
||||
def packages_rebuild(self, depends_on: str) -> None:
|
||||
def packages_rebuild(self, depends_on: str, username: str | None) -> None:
|
||||
"""
|
||||
rebuild packages which depend on the specified package
|
||||
|
||||
Args:
|
||||
depends_on(str): packages dependency
|
||||
username(str | None): optional override of username for build process
|
||||
"""
|
||||
self._spawn_process("repo-rebuild", **{"depends-on": depends_on})
|
||||
kwargs = {"depends-on": depends_on, "username": username}
|
||||
self._spawn_process("repo-rebuild", **kwargs)
|
||||
|
||||
def packages_remove(self, packages: Iterable[str]) -> None:
|
||||
"""
|
||||
@ -153,11 +159,15 @@ class Spawn(Thread, LazyLogging):
|
||||
"""
|
||||
self._spawn_process("package-remove", *packages)
|
||||
|
||||
def packages_update(self) -> None:
|
||||
def packages_update(self, username: str | None) -> None:
|
||||
"""
|
||||
run full repository update
|
||||
|
||||
Args:
|
||||
username(str | None): optional override of username for build process
|
||||
"""
|
||||
self._spawn_process("repo-update")
|
||||
kwargs = {"username": username}
|
||||
self._spawn_process("repo-update", **kwargs)
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
|
@ -19,6 +19,7 @@
|
||||
#
|
||||
from ahriman.core import context
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.support.package_creator import PackageCreator
|
||||
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
|
||||
@ -107,8 +108,9 @@ class KeyringTrigger(Trigger):
|
||||
"""
|
||||
ctx = context.get()
|
||||
sign = ctx.get(ContextKey("sign", GPG))
|
||||
database = ctx.get(ContextKey("database", SQLite))
|
||||
|
||||
for target in self.targets:
|
||||
generator = KeyringGenerator(sign, self.configuration, target)
|
||||
generator = KeyringGenerator(database, sign, self.configuration, target)
|
||||
runner = PackageCreator(self.configuration, generator)
|
||||
runner.run()
|
||||
|
@ -67,5 +67,5 @@ class PackageCreator:
|
||||
ctx = context.get()
|
||||
database: SQLite = ctx.get(ContextKey("database", SQLite))
|
||||
_, architecture = self.configuration.check_loaded()
|
||||
package = Package.from_build(local_path, architecture)
|
||||
package = Package.from_build(local_path, architecture, None)
|
||||
database.package_update(package, BuildStatus())
|
||||
|
@ -21,6 +21,7 @@ from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import PkgbuildGeneratorError
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
|
||||
@ -42,11 +43,12 @@ class KeyringGenerator(PkgbuildGenerator):
|
||||
trusted(list[str]): lif of trusted PGP keys
|
||||
"""
|
||||
|
||||
def __init__(self, sign: GPG, configuration: Configuration, section: str) -> None:
|
||||
def __init__(self, database: SQLite, sign: GPG, configuration: Configuration, section: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
database(SQLite): database instance
|
||||
sign(GPG): GPG wrapper instance
|
||||
configuration(Configuration): configuration instance
|
||||
section(str): settings section name
|
||||
@ -55,7 +57,8 @@ class KeyringGenerator(PkgbuildGenerator):
|
||||
self.name = configuration.repository_name
|
||||
|
||||
# configuration fields
|
||||
self.packagers = configuration.getlist(section, "packagers", fallback=sign.keys())
|
||||
packager_keys = [packager.key for packager in database.user_list(None, None) if packager.key is not None]
|
||||
self.packagers = configuration.getlist(section, "packagers", fallback=packager_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 [])
|
||||
@ -148,10 +151,10 @@ class KeyringGenerator(PkgbuildGenerator):
|
||||
|
||||
def install(self) -> str | None:
|
||||
"""
|
||||
content of the install functions
|
||||
content of the .install functions
|
||||
|
||||
Returns:
|
||||
str | None: content of the install functions if any
|
||||
str | None: content of the .install functions if any
|
||||
"""
|
||||
# copy-paste from archlinux-keyring
|
||||
return f"""post_upgrade() {{
|
||||
|
@ -98,10 +98,10 @@ class PkgbuildGenerator:
|
||||
|
||||
def install(self) -> str | None:
|
||||
"""
|
||||
content of the install functions
|
||||
content of the .install functions
|
||||
|
||||
Returns:
|
||||
str | None: content of the install functions if any
|
||||
str | None: content of the .install functions if any
|
||||
"""
|
||||
|
||||
def package(self) -> str:
|
||||
|
@ -28,6 +28,7 @@ import requests
|
||||
import subprocess
|
||||
|
||||
from collections.abc import Callable, Generator, Iterable
|
||||
from dataclasses import asdict
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from pwd import getpwuid
|
||||
@ -40,8 +41,10 @@ from ahriman.models.repository_paths import RepositoryPaths
|
||||
__all__ = [
|
||||
"check_output",
|
||||
"check_user",
|
||||
"dataclass_view",
|
||||
"enum_values",
|
||||
"exception_response_text",
|
||||
"extract_user",
|
||||
"filter_json",
|
||||
"full_version",
|
||||
"package_like",
|
||||
@ -61,7 +64,8 @@ T = TypeVar("T")
|
||||
|
||||
|
||||
def check_output(*args: str, exception: Exception | None = None, cwd: Path | None = None, input_data: str | None = None,
|
||||
logger: logging.Logger | None = None, user: int | None = None) -> str:
|
||||
logger: logging.Logger | None = None, user: int | None = None,
|
||||
environment: dict[str, str] | None = None) -> str:
|
||||
"""
|
||||
subprocess wrapper
|
||||
|
||||
@ -73,6 +77,7 @@ def check_output(*args: str, exception: Exception | None = None, cwd: Path | Non
|
||||
input_data(str | None, optional): data which will be written to command stdin (Default value = None)
|
||||
logger(logging.Logger | None, optional): logger to log command result if required (Default value = None)
|
||||
user(int | None, optional): run process as specified user (Default value = None)
|
||||
environment(dict[str, str] | None, optional): optional environment variables if any (Default value = None)
|
||||
|
||||
Returns:
|
||||
str: command output
|
||||
@ -106,7 +111,9 @@ def check_output(*args: str, exception: Exception | None = None, cwd: Path | Non
|
||||
if logger is not None:
|
||||
logger.debug(single)
|
||||
|
||||
environment = {"HOME": getpwuid(user).pw_dir} if user is not None else {}
|
||||
environment = environment or {}
|
||||
if user is not None:
|
||||
environment["HOME"] = getpwuid(user).pw_dir
|
||||
# FIXME additional workaround for linter and type check which do not know that user arg is supported
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
with subprocess.Popen(args, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
@ -163,6 +170,19 @@ def check_user(paths: RepositoryPaths, *, unsafe: bool) -> None:
|
||||
raise UnsafeRunError(current_uid, root_uid)
|
||||
|
||||
|
||||
def dataclass_view(instance: Any) -> dict[str, Any]:
|
||||
"""
|
||||
convert dataclass instance to json object
|
||||
|
||||
Args:
|
||||
instance(Any): dataclass instance
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: json representation of the dataclass with empty field removed
|
||||
"""
|
||||
return asdict(instance, dict_factory=lambda fields: {key: value for key, value in fields if value is not None})
|
||||
|
||||
|
||||
def enum_values(enum: type[Enum]) -> list[str]:
|
||||
"""
|
||||
generate list of enumeration values from the source
|
||||
@ -190,6 +210,17 @@ def exception_response_text(exception: requests.exceptions.RequestException) ->
|
||||
return result
|
||||
|
||||
|
||||
def extract_user() -> str | None:
|
||||
"""
|
||||
extract user from system environment
|
||||
|
||||
Returns:
|
||||
str | None: SUDO_USER in case if set and USER otherwise. It can return None in case if environment has been
|
||||
cleared before application start
|
||||
"""
|
||||
return os.getenv("SUDO_USER") or os.getenv("DOAS_USER") or os.getenv("USER")
|
||||
|
||||
|
||||
def filter_json(source: dict[str, Any], known_fields: Iterable[str]) -> dict[str, Any]:
|
||||
"""
|
||||
filter json object by fields used for json-to-object conversion
|
||||
|
@ -17,9 +17,10 @@
|
||||
# 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 dataclasses import asdict, dataclass, field
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Self
|
||||
|
||||
from ahriman.core.util import dataclass_view
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.counters import Counters
|
||||
|
||||
@ -69,4 +70,4 @@ class InternalStatus:
|
||||
Returns:
|
||||
dict[str, Any]: json-friendly dictionary
|
||||
"""
|
||||
return asdict(self)
|
||||
return dataclass_view(self)
|
||||
|
@ -23,7 +23,7 @@ from __future__ import annotations
|
||||
import copy
|
||||
|
||||
from collections.abc import Callable, Generator, Iterable
|
||||
from dataclasses import asdict, dataclass
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from pyalpm import vercmp # type: ignore[import]
|
||||
from srcinfo.parse import parse_srcinfo # type: ignore[import]
|
||||
@ -34,7 +34,7 @@ from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb
|
||||
from ahriman.core.exceptions import PackageInfoError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output, full_version, srcinfo_property_list, utcnow
|
||||
from ahriman.core.util import check_output, dataclass_view, full_version, srcinfo_property_list, utcnow
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
@ -48,6 +48,7 @@ class Package(LazyLogging):
|
||||
|
||||
Attributes:
|
||||
base(str): package base name
|
||||
packager(str | None): package packager if available
|
||||
packages(dict[str, PackageDescription): map of package names to their properties.
|
||||
Filled only on load from archive
|
||||
remote(RemoteSource | None): package remote source if applicable
|
||||
@ -77,6 +78,7 @@ class Package(LazyLogging):
|
||||
version: str
|
||||
remote: RemoteSource | None
|
||||
packages: dict[str, PackageDescription]
|
||||
packager: str | None = None
|
||||
|
||||
_check_output = check_output
|
||||
|
||||
@ -204,16 +206,18 @@ class Package(LazyLogging):
|
||||
"""
|
||||
package = pacman.handle.load_pkg(str(path))
|
||||
description = PackageDescription.from_package(package, path)
|
||||
return cls(base=package.base, version=package.version, remote=remote, packages={package.name: description})
|
||||
return cls(base=package.base, version=package.version, remote=remote, packages={package.name: description},
|
||||
packager=package.packager)
|
||||
|
||||
@classmethod
|
||||
def from_aur(cls, name: str, pacman: Pacman) -> Self:
|
||||
def from_aur(cls, name: str, pacman: Pacman, packager: str | None = None) -> Self:
|
||||
"""
|
||||
construct package properties from AUR page
|
||||
|
||||
Args:
|
||||
name(str): package name (either base or normal name)
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
packager(str | None, optional): packager to be used for this build (Default value = None)
|
||||
|
||||
Returns:
|
||||
Self: package properties
|
||||
@ -224,16 +228,19 @@ class Package(LazyLogging):
|
||||
base=package.package_base,
|
||||
version=package.version,
|
||||
remote=remote,
|
||||
packages={package.name: PackageDescription.from_aur(package)})
|
||||
packages={package.name: PackageDescription.from_aur(package)},
|
||||
packager=packager,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_build(cls, path: Path, architecture: str) -> Self:
|
||||
def from_build(cls, path: Path, architecture: str, packager: str | None = None) -> Self:
|
||||
"""
|
||||
construct package properties from sources directory
|
||||
|
||||
Args:
|
||||
path(Path): path to package sources directory
|
||||
architecture(str): load package for specific architecture
|
||||
packager(str | None, optional): packager to be used for this build (Default value = None)
|
||||
|
||||
Returns:
|
||||
Self: package properties
|
||||
@ -265,7 +272,7 @@ class Package(LazyLogging):
|
||||
source=PackageSource.Local,
|
||||
)
|
||||
|
||||
return cls(base=srcinfo["pkgbase"], version=version, remote=remote, packages=packages)
|
||||
return cls(base=srcinfo["pkgbase"], version=version, remote=remote, packages=packages, packager=packager)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, dump: dict[str, Any]) -> Self:
|
||||
@ -284,16 +291,18 @@ class Package(LazyLogging):
|
||||
for key, value in packages_json.items()
|
||||
}
|
||||
remote = dump.get("remote") or {}
|
||||
return cls(base=dump["base"], version=dump["version"], remote=RemoteSource.from_json(remote), packages=packages)
|
||||
return cls(base=dump["base"], version=dump["version"], remote=RemoteSource.from_json(remote), packages=packages,
|
||||
packager=dump.get("packager"))
|
||||
|
||||
@classmethod
|
||||
def from_official(cls, name: str, pacman: Pacman, *, use_syncdb: bool = True) -> Self:
|
||||
def from_official(cls, name: str, pacman: Pacman, packager: str | None = None, *, use_syncdb: bool = True) -> Self:
|
||||
"""
|
||||
construct package properties from official repository page
|
||||
|
||||
Args:
|
||||
name(str): package name (either base or normal name)
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
packager(str | None, optional): packager to be used for this build (Default value = None)
|
||||
use_syncdb(bool, optional): use pacman databases instead of official repositories RPC (Default value = True)
|
||||
|
||||
Returns:
|
||||
@ -305,7 +314,9 @@ class Package(LazyLogging):
|
||||
base=package.package_base,
|
||||
version=package.version,
|
||||
remote=remote,
|
||||
packages={package.name: PackageDescription.from_aur(package)})
|
||||
packages={package.name: PackageDescription.from_aur(package)},
|
||||
packager=packager,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def local_files(path: Path) -> Generator[Path, None, None]:
|
||||
@ -513,4 +524,4 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
dict[str, Any]: json-friendly dictionary
|
||||
"""
|
||||
return asdict(self)
|
||||
return dataclass_view(self)
|
||||
|
@ -17,12 +17,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from dataclasses import dataclass, field, fields
|
||||
from pathlib import Path
|
||||
from pyalpm import Package # type: ignore[import]
|
||||
from typing import Any, Self
|
||||
|
||||
from ahriman.core.util import filter_json, trim_package
|
||||
from ahriman.core.util import dataclass_view, filter_json, trim_package
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
@ -172,4 +172,4 @@ class PackageDescription:
|
||||
Returns:
|
||||
dict[str, Any]: json-friendly dictionary
|
||||
"""
|
||||
return asdict(self)
|
||||
return dataclass_view(self)
|
||||
|
46
src/ahriman/models/packagers.py
Normal file
46
src/ahriman/models/packagers.py
Normal file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# 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 dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Packagers:
|
||||
"""
|
||||
holder for packagers overrides
|
||||
|
||||
Attributes:
|
||||
default(str | None): default packager username if any to be used if no override for the specified base was found
|
||||
overrides: dict[str, str | None]: packager username override for specific package base
|
||||
"""
|
||||
|
||||
default: str | None = None
|
||||
overrides: dict[str, str | None] = field(default_factory=dict)
|
||||
|
||||
def for_base(self, package_base: str) -> str | None:
|
||||
"""
|
||||
extract username for the specified package base
|
||||
|
||||
Args:
|
||||
package_base(str): package base to lookup
|
||||
|
||||
Returns:
|
||||
str | None: package base override if set and default packager username otherwise
|
||||
"""
|
||||
return self.overrides.get(package_base) or self.default
|
@ -17,11 +17,11 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from dataclasses import dataclass, fields
|
||||
from pathlib import Path
|
||||
from typing import Any, Self
|
||||
|
||||
from ahriman.core.util import filter_json
|
||||
from ahriman.core.util import dataclass_view, filter_json
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
@ -118,4 +118,4 @@ class RemoteSource:
|
||||
Returns:
|
||||
dict[str, Any]: json-friendly dictionary
|
||||
"""
|
||||
return asdict(self)
|
||||
return dataclass_view(self)
|
||||
|
@ -34,12 +34,14 @@ class User:
|
||||
username(str): username
|
||||
password(str): hashed user password with salt
|
||||
access(UserAccess): user role
|
||||
packager_id(str | None): packager id to be used. If not set, the default service packager will be used
|
||||
key(str | None): personal packager key if any. If user id is empty, it is interpreted as default key
|
||||
|
||||
Examples:
|
||||
Simply create user from database data and perform required validation::
|
||||
|
||||
>>> password = User.generate_password(24)
|
||||
>>> user = User("ahriman", password, UserAccess.Full)
|
||||
>>> user = User(username="ahriman", password=password, access=UserAccess.Full, packager_id=None, key=None)
|
||||
|
||||
Since the password supplied may be plain text, the ``hash_password`` method can be used to hash the password::
|
||||
|
||||
@ -61,9 +63,18 @@ class User:
|
||||
username: str
|
||||
password: str
|
||||
access: UserAccess
|
||||
packager_id: str | None
|
||||
key: str | None
|
||||
|
||||
_HASHER = sha512_crypt
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
remove empty fields
|
||||
"""
|
||||
object.__setattr__(self, "packager_id", self.packager_id or None)
|
||||
object.__setattr__(self, "key", self.key or None)
|
||||
|
||||
@classmethod
|
||||
def from_option(cls, username: str | None, password: str | None,
|
||||
access: UserAccess = UserAccess.Read) -> Self | None:
|
||||
@ -80,7 +91,7 @@ class User:
|
||||
"""
|
||||
if username is None or password is None:
|
||||
return None
|
||||
return cls(username=username, password=password, access=access)
|
||||
return cls(username=username, password=password, access=access, packager_id=None, key=None)
|
||||
|
||||
@staticmethod
|
||||
def generate_password(length: int) -> str:
|
||||
@ -149,4 +160,4 @@ class User:
|
||||
Returns:
|
||||
str: unique string representation
|
||||
"""
|
||||
return f"User(username={self.username}, access={self.access})"
|
||||
return f"User(username={self.username}, access={self.access}, packager_id={self.packager_id}, key={self.key})"
|
||||
|
@ -148,7 +148,7 @@ def setup_auth(application: Application, configuration: Configuration, validator
|
||||
setup_session(application, storage)
|
||||
|
||||
authorization_policy = _AuthorizationPolicy(validator)
|
||||
identity_policy = aiohttp_security.SessionIdentityPolicy()
|
||||
identity_policy = application["identity"] = aiohttp_security.SessionIdentityPolicy()
|
||||
|
||||
aiohttp_security.setup(application, identity_policy, authorization_policy)
|
||||
application.middlewares.append(_auth_handler(validator.allow_read_only))
|
||||
|
@ -44,3 +44,7 @@ class PackageSchema(Schema):
|
||||
keys=fields.String(), values=fields.Nested(PackagePropertiesSchema()), required=True, metadata={
|
||||
"description": "Packages which belong to this base",
|
||||
})
|
||||
packager = fields.String(metadata={
|
||||
"description": "packager for the last success package build",
|
||||
"example": "John Doe <john@doe.com>",
|
||||
})
|
||||
|
@ -183,3 +183,16 @@ class BaseView(View, CorsViewMixin):
|
||||
return response
|
||||
|
||||
self._raise_allowed_methods()
|
||||
|
||||
async def username(self) -> str | None:
|
||||
"""
|
||||
extract username from request if any
|
||||
|
||||
Returns:
|
||||
str | None: authorized username if any and None otherwise (e.g. if authorization is disabled)
|
||||
"""
|
||||
policy = self.request.app.get("identity")
|
||||
if policy is not None:
|
||||
identity: str = await policy.identify(self.request)
|
||||
return identity
|
||||
return None
|
||||
|
@ -67,6 +67,7 @@ class AddView(BaseView):
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
self.spawner.packages_add(packages, now=True)
|
||||
username = await self.username()
|
||||
self.spawner.packages_add(packages, username, now=True)
|
||||
|
||||
raise HTTPNoContent()
|
||||
|
@ -68,6 +68,7 @@ class RebuildView(BaseView):
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
self.spawner.packages_rebuild(depends_on)
|
||||
username = await self.username()
|
||||
self.spawner.packages_rebuild(depends_on, username)
|
||||
|
||||
raise HTTPNoContent()
|
||||
|
@ -67,6 +67,7 @@ class RequestView(BaseView):
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
self.spawner.packages_add(packages, now=False)
|
||||
username = await self.username()
|
||||
self.spawner.packages_add(packages, username, now=False)
|
||||
|
||||
raise HTTPNoContent()
|
||||
|
@ -57,6 +57,7 @@ class UpdateView(BaseView):
|
||||
Raises:
|
||||
HTTPNoContent: in case of success response
|
||||
"""
|
||||
self.spawner.packages_update()
|
||||
username = await self.username()
|
||||
self.spawner.packages_update(username)
|
||||
|
||||
raise HTTPNoContent()
|
||||
|
@ -72,16 +72,16 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
|
||||
"python-installer": create_package_mock("python-installer"),
|
||||
}
|
||||
|
||||
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 *args: packages[args[0]])
|
||||
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages",
|
||||
return_value=["devtools", "python-build", "python-pytest"])
|
||||
return_value={"devtools", "python-build", "python-pytest"})
|
||||
|
||||
result = application.with_dependencies([package_ahriman], process_dependencies=True)
|
||||
assert {package.base: package for package in result} == packages
|
||||
package_mock.assert_has_calls([
|
||||
MockCall(package_python_schedule.base, application.repository.pacman),
|
||||
MockCall("python", application.repository.pacman),
|
||||
MockCall("python-installer", application.repository.pacman),
|
||||
MockCall(package_python_schedule.base, application.repository.pacman, package_ahriman.packager),
|
||||
MockCall("python", application.repository.pacman, package_ahriman.packager),
|
||||
MockCall("python-installer", application.repository.pacman, package_ahriman.packager),
|
||||
], any_order=True)
|
||||
packages_mock.assert_called_once_with()
|
||||
|
||||
|
@ -43,7 +43,7 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac
|
||||
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
|
||||
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.remote_update")
|
||||
|
||||
application_packages._add_aur(package_ahriman.base)
|
||||
application_packages._add_aur(package_ahriman.base, "packager")
|
||||
build_queue_mock.assert_called_once_with(package_ahriman)
|
||||
update_remote_mock.assert_called_once_with(package_ahriman)
|
||||
|
||||
@ -83,7 +83,7 @@ def test_add_local(application_packages: ApplicationPackages, package_ahriman: P
|
||||
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)
|
||||
application_packages._add_local(package_ahriman.base, "packager")
|
||||
is_dir_mock.assert_called_once_with()
|
||||
copytree_mock.assert_called_once_with(
|
||||
Path(package_ahriman.base), application_packages.repository.paths.cache_for(package_ahriman.base))
|
||||
@ -103,7 +103,7 @@ def test_add_local_cache(application_packages: ApplicationPackages, package_ahri
|
||||
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)
|
||||
application_packages._add_local(package_ahriman.base, "packager")
|
||||
copytree_mock.assert_not_called()
|
||||
init_mock.assert_not_called()
|
||||
build_queue_mock.assert_called_once_with(package_ahriman)
|
||||
@ -115,7 +115,7 @@ def test_add_local_missing(application_packages: ApplicationPackages, mocker: Mo
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
with pytest.raises(UnknownPackageError):
|
||||
application_packages._add_local("package")
|
||||
application_packages._add_local("package", "packager")
|
||||
|
||||
|
||||
def test_add_remote(application_packages: ApplicationPackages, package_description_ahriman: PackageDescription,
|
||||
@ -153,7 +153,7 @@ def test_add_repository(application_packages: ApplicationPackages, package_ahrim
|
||||
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
|
||||
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.remote_update")
|
||||
|
||||
application_packages._add_repository(package_ahriman.base)
|
||||
application_packages._add_repository(package_ahriman.base, "packager")
|
||||
build_queue_mock.assert_called_once_with(package_ahriman)
|
||||
update_remote_mock.assert_called_once_with(package_ahriman)
|
||||
|
||||
@ -165,8 +165,8 @@ def test_add_add_archive(application_packages: ApplicationPackages, package_ahri
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_archive")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.Archive)
|
||||
add_mock.assert_called_once_with(package_ahriman.base)
|
||||
application_packages.add([package_ahriman.base], PackageSource.Archive, "packager")
|
||||
add_mock.assert_called_once_with(package_ahriman.base, "packager")
|
||||
|
||||
|
||||
def test_add_add_aur(application_packages: ApplicationPackages, package_ahriman: Package,
|
||||
@ -176,8 +176,8 @@ def test_add_add_aur(application_packages: ApplicationPackages, package_ahriman:
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_aur")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.AUR)
|
||||
add_mock.assert_called_once_with(package_ahriman.base)
|
||||
application_packages.add([package_ahriman.base], PackageSource.AUR, "packager")
|
||||
add_mock.assert_called_once_with(package_ahriman.base, "packager")
|
||||
|
||||
|
||||
def test_add_add_directory(application_packages: ApplicationPackages, package_ahriman: Package,
|
||||
@ -187,8 +187,8 @@ def test_add_add_directory(application_packages: ApplicationPackages, package_ah
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_directory")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.Directory)
|
||||
add_mock.assert_called_once_with(package_ahriman.base)
|
||||
application_packages.add([package_ahriman.base], PackageSource.Directory, "packager")
|
||||
add_mock.assert_called_once_with(package_ahriman.base, "packager")
|
||||
|
||||
|
||||
def test_add_add_local(application_packages: ApplicationPackages, package_ahriman: Package,
|
||||
@ -198,8 +198,8 @@ def test_add_add_local(application_packages: ApplicationPackages, package_ahrima
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_local")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.Local)
|
||||
add_mock.assert_called_once_with(package_ahriman.base)
|
||||
application_packages.add([package_ahriman.base], PackageSource.Local, "packager")
|
||||
add_mock.assert_called_once_with(package_ahriman.base, "packager")
|
||||
|
||||
|
||||
def test_add_add_remote(application_packages: ApplicationPackages, package_description_ahriman: PackageDescription,
|
||||
@ -210,8 +210,8 @@ def test_add_add_remote(application_packages: ApplicationPackages, package_descr
|
||||
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_remote")
|
||||
url = f"https://host/{package_description_ahriman.filename}"
|
||||
|
||||
application_packages.add([url], PackageSource.Remote)
|
||||
add_mock.assert_called_once_with(url)
|
||||
application_packages.add([url], PackageSource.Remote, "packager")
|
||||
add_mock.assert_called_once_with(url, "packager")
|
||||
|
||||
|
||||
def test_on_result(application_packages: ApplicationPackages) -> None:
|
||||
|
@ -76,9 +76,9 @@ def test_sign(application_repository: ApplicationRepository, package_ahriman: Pa
|
||||
|
||||
application_repository.sign([])
|
||||
sign_package_mock.assert_has_calls([
|
||||
MockCall(pytest.helpers.anyvar(int), package_ahriman.base),
|
||||
MockCall(pytest.helpers.anyvar(int), package_python_schedule.base),
|
||||
MockCall(pytest.helpers.anyvar(int), package_python_schedule.base),
|
||||
MockCall(pytest.helpers.anyvar(int), None),
|
||||
MockCall(pytest.helpers.anyvar(int), None),
|
||||
MockCall(pytest.helpers.anyvar(int), None),
|
||||
])
|
||||
sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path)
|
||||
on_result_mock.assert_called_once_with(Result())
|
||||
@ -111,7 +111,7 @@ def test_sign_specific(application_repository: ApplicationRepository, package_ah
|
||||
|
||||
filename = package_ahriman.packages[package_ahriman.base].filepath
|
||||
application_repository.sign([package_ahriman.base])
|
||||
sign_package_mock.assert_called_once_with(filename, package_ahriman.base)
|
||||
sign_package_mock.assert_called_once_with(filename, None)
|
||||
sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path)
|
||||
on_result_mock.assert_called_once_with(Result())
|
||||
|
||||
@ -170,9 +170,9 @@ def test_update(application_repository: ApplicationRepository, package_ahriman:
|
||||
on_result_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository.on_result")
|
||||
|
||||
application_repository.update([package_ahriman])
|
||||
build_mock.assert_called_once_with([package_ahriman])
|
||||
update_mock.assert_has_calls([MockCall(paths), MockCall(paths)])
|
||||
application_repository.update([package_ahriman], "username")
|
||||
build_mock.assert_called_once_with([package_ahriman], "username")
|
||||
update_mock.assert_has_calls([MockCall(paths, "username"), MockCall(paths, "username")])
|
||||
on_result_mock.assert_has_calls([MockCall(result), MockCall(result)])
|
||||
|
||||
|
||||
|
@ -8,6 +8,7 @@ from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
@ -27,6 +28,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
args.refresh = 0
|
||||
args.source = PackageSource.Auto
|
||||
args.dependencies = True
|
||||
args.username = "username"
|
||||
return args
|
||||
|
||||
|
||||
@ -42,7 +44,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Add.run(args, "x86_64", configuration, report=False, unsafe=False)
|
||||
application_mock.assert_called_once_with(args.package, args.source)
|
||||
application_mock.assert_called_once_with(args.package, args.source, args.username)
|
||||
dependencies_mock.assert_not_called()
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
||||
@ -67,7 +69,8 @@ def test_run_with_updates(args: argparse.Namespace, configuration: Configuration
|
||||
Add.run(args, "x86_64", configuration, report=False, unsafe=False)
|
||||
updates_mock.assert_called_once_with(args.package, aur=False, local=False, manual=True, vcs=False,
|
||||
log_fn=pytest.helpers.anyvar(int))
|
||||
application_mock.assert_called_once_with([package_ahriman])
|
||||
application_mock.assert_called_once_with([package_ahriman],
|
||||
Packagers(args.username, {package_ahriman.base: "packager"}))
|
||||
dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies)
|
||||
check_mock.assert_called_once_with(False, False)
|
||||
|
||||
|
@ -109,7 +109,7 @@ def test_patch_create_from_diff(package_ahriman: Package, mocker: MockerFixture)
|
||||
sources_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_create", return_value=patch.value)
|
||||
|
||||
assert Patch.patch_create_from_diff(path, "x86_64", ["*.diff"]) == (package_ahriman.base, patch)
|
||||
package_mock.assert_called_once_with(path, "x86_64")
|
||||
package_mock.assert_called_once_with(path, "x86_64", None)
|
||||
sources_mock.assert_called_once_with(path, "*.diff")
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
args.from_database = False
|
||||
args.exit_code = False
|
||||
args.status = None
|
||||
args.username = "username"
|
||||
return args
|
||||
|
||||
|
||||
@ -50,7 +51,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
|
||||
Rebuild.run(args, "x86_64", configuration, report=False, unsafe=False)
|
||||
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_mock.assert_called_once_with([package_ahriman])
|
||||
application_mock.assert_called_once_with([package_ahriman], args.username)
|
||||
check_mock.assert_has_calls([MockCall(False, False), MockCall(False, False)])
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
||||
|
@ -36,7 +36,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
|
||||
ServiceUpdates.run(args, "x86_64", configuration, report=False, unsafe=False)
|
||||
package_mock.assert_called_once_with(package_ahriman.base, repository.pacman)
|
||||
package_mock.assert_called_once_with(package_ahriman.base, repository.pacman, None)
|
||||
application_mock.assert_called_once_with(verbose=True, separator=" -> ")
|
||||
check_mock.assert_called_once_with(args.exit_code, True)
|
||||
|
||||
|
@ -19,7 +19,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
argparse.Namespace: generated arguments for these test cases
|
||||
"""
|
||||
args.parser = _parser
|
||||
args.command = None
|
||||
args.command = []
|
||||
return args
|
||||
|
||||
|
||||
@ -42,14 +42,14 @@ def test_run_check(args: argparse.Namespace, configuration: Configuration, mocke
|
||||
must run command and check if command is unsafe
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.command = "clean"
|
||||
args.command = ["clean"]
|
||||
commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands",
|
||||
return_value=["command"])
|
||||
check_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.check_unsafe")
|
||||
|
||||
UnsafeCommands.run(args, "x86_64", configuration, report=False, unsafe=False)
|
||||
commands_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
check_mock.assert_called_once_with("clean", ["command"], pytest.helpers.anyvar(int))
|
||||
check_mock.assert_called_once_with(["clean"], ["command"], pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_check_unsafe(mocker: MockerFixture) -> None:
|
||||
@ -57,7 +57,7 @@ def test_check_unsafe(mocker: MockerFixture) -> None:
|
||||
must check if command is unsafe
|
||||
"""
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
UnsafeCommands.check_unsafe("service-clean", ["service-clean"], _parser())
|
||||
UnsafeCommands.check_unsafe(["service-clean"], ["service-clean"], _parser())
|
||||
check_mock.assert_called_once_with(True, True)
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ def test_check_unsafe_safe(mocker: MockerFixture) -> None:
|
||||
must check if command is safe
|
||||
"""
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
UnsafeCommands.check_unsafe("package-status", ["service-clean"], _parser())
|
||||
UnsafeCommands.check_unsafe(["package-status"], ["service-clean"], _parser())
|
||||
check_mock.assert_called_once_with(True, False)
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@ from ahriman.application.handlers import Update
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
@ -31,6 +32,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
args.manual = True
|
||||
args.vcs = True
|
||||
args.refresh = 0
|
||||
args.username = "username"
|
||||
return args
|
||||
|
||||
|
||||
@ -51,7 +53,8 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Update.run(args, "x86_64", configuration, report=False, unsafe=False)
|
||||
application_mock.assert_called_once_with([package_ahriman])
|
||||
application_mock.assert_called_once_with([package_ahriman],
|
||||
Packagers(args.username, {package_ahriman.base: "packager"}))
|
||||
updates_mock.assert_called_once_with(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
|
||||
log_fn=pytest.helpers.anyvar(int))
|
||||
dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies)
|
||||
|
@ -27,6 +27,8 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
args.username = "user"
|
||||
args.action = Action.Update
|
||||
args.exit_code = False
|
||||
args.key = "key"
|
||||
args.packager = "packager"
|
||||
args.password = "pa55w0rd"
|
||||
args.role = UserAccess.Reporter
|
||||
args.secure = False
|
||||
@ -38,7 +40,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, database: S
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
user = User(username=args.username, password=args.password, access=args.role)
|
||||
user = User(username=args.username, password=args.password, access=args.role,
|
||||
packager_id=args.packager, key=args.key)
|
||||
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
|
||||
mocker.patch("ahriman.models.user.User.hash_password", return_value=user)
|
||||
get_auth_configuration_mock = mocker.patch("ahriman.application.handlers.Users.configuration_get")
|
||||
@ -61,7 +64,8 @@ def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration,
|
||||
must create configuration if salt was not set
|
||||
"""
|
||||
args = _default_args(args)
|
||||
user = User(username=args.username, password=args.password, access=args.role)
|
||||
user = User(username=args.username, password=args.password, access=args.role,
|
||||
packager_id=args.packager, key=args.key)
|
||||
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
|
||||
mocker.patch("ahriman.models.user.User.hash_password", return_value=user)
|
||||
get_auth_configuration_mock = mocker.patch("ahriman.application.handlers.Users.configuration_get")
|
||||
|
@ -351,13 +351,14 @@ def test_subparsers_repo_backup_architecture(parser: argparse.ArgumentParser) ->
|
||||
|
||||
def test_subparsers_repo_check(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
repo-check command must imply dependencies, dry-run, aur and manual
|
||||
repo-check command must imply dependencies, dry-run, aur, manual and username
|
||||
"""
|
||||
args = parser.parse_args(["repo-check"])
|
||||
assert not args.dependencies
|
||||
assert args.dry_run
|
||||
assert args.aur
|
||||
assert not args.manual
|
||||
assert args.username is None
|
||||
|
||||
|
||||
def test_subparsers_repo_check_architecture(parser: argparse.ArgumentParser) -> None:
|
||||
@ -757,14 +758,13 @@ def test_subparsers_user_add_option_role(parser: argparse.ArgumentParser) -> Non
|
||||
|
||||
def test_subparsers_user_list(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
user-list command must imply action, architecture, lock, report, password, quiet and unsafe
|
||||
user-list command must imply action, architecture, lock, report, quiet and unsafe
|
||||
"""
|
||||
args = parser.parse_args(["user-list"])
|
||||
assert args.action == Action.List
|
||||
assert args.architecture == [""]
|
||||
assert args.lock is None
|
||||
assert not args.report
|
||||
assert args.password is not None
|
||||
assert args.quiet
|
||||
assert args.unsafe
|
||||
|
||||
@ -787,14 +787,13 @@ def test_subparsers_user_list_option_role(parser: argparse.ArgumentParser) -> No
|
||||
|
||||
def test_subparsers_user_remove(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
user-remove command must imply action, architecture, lock, report, password and quiet
|
||||
user-remove command must imply action, architecture, lock, report and quiet
|
||||
"""
|
||||
args = parser.parse_args(["user-remove", "username"])
|
||||
assert args.action == Action.Remove
|
||||
assert args.architecture == [""]
|
||||
assert args.lock is None
|
||||
assert not args.report
|
||||
assert args.password is not None
|
||||
assert args.quiet
|
||||
|
||||
|
||||
|
@ -265,7 +265,8 @@ def package_ahriman(package_description_ahriman: PackageDescription, remote_sour
|
||||
base="ahriman",
|
||||
version="2.6.0-1",
|
||||
remote=remote_source,
|
||||
packages=packages)
|
||||
packages=packages,
|
||||
packager="packager")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -499,7 +500,7 @@ def user() -> User:
|
||||
Returns:
|
||||
User: user descriptor instance
|
||||
"""
|
||||
return User(username="user", password="pa55w0rd", access=UserAccess.Reporter)
|
||||
return User(username="user", password="pa55w0rd", access=UserAccess.Reporter, packager_id="packager", key="key")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -10,7 +10,7 @@ def test_build(task_ahriman: Task, mocker: MockerFixture) -> None:
|
||||
must build package
|
||||
"""
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.task.Task._check_output")
|
||||
task_ahriman.build(Path("ahriman"))
|
||||
task_ahriman.build(Path("ahriman"), "packager")
|
||||
check_output_mock.assert_called()
|
||||
|
||||
|
||||
|
@ -27,7 +27,7 @@ def test_migrate_data(connection: Connection, configuration: Configuration, mock
|
||||
def test_migrate_package_depends(connection: Connection, configuration: Configuration, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must update make and opt depends list
|
||||
must update check 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])
|
||||
@ -45,7 +45,7 @@ def test_migrate_package_depends(connection: Connection, configuration: Configur
|
||||
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
|
||||
must skip update check depends list if no repository directory found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
migrate_package_check_depends(connection, configuration)
|
||||
|
@ -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.m008_packagers import migrate_data, migrate_package_base_packager, steps
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_migration_packagers() -> 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.m008_packagers.migrate_package_base_packager")
|
||||
migrate_data(connection, configuration)
|
||||
depends_mock.assert_called_once_with(connection, configuration)
|
||||
|
||||
|
||||
def test_migrate_package_base_packager(connection: Connection, configuration: Configuration, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must update packagers
|
||||
"""
|
||||
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_base_packager(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), [{
|
||||
"package_base": package_ahriman.base,
|
||||
"packager": package_ahriman.packager,
|
||||
}])
|
||||
|
||||
|
||||
def test_migrate_package_depends_skip(connection: Connection, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip update packagers if no repository directory found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
migrate_package_base_packager(connection, configuration)
|
||||
connection.executemany.assert_not_called()
|
@ -16,21 +16,22 @@ def test_user_list(database: SQLite, user: User) -> None:
|
||||
must return all users
|
||||
"""
|
||||
database.user_update(user)
|
||||
database.user_update(User(username=user.password, password=user.username, access=user.access))
|
||||
second = User(username=user.password, password=user.username, access=user.access, packager_id=None, key=None)
|
||||
database.user_update(second)
|
||||
|
||||
users = database.user_list(None, None)
|
||||
assert len(users) == 2
|
||||
assert user in users
|
||||
assert User(username=user.password, password=user.username, access=user.access) in users
|
||||
assert second in users
|
||||
|
||||
|
||||
def test_user_list_filter_by_username(database: SQLite) -> None:
|
||||
"""
|
||||
must return users filtered by its id
|
||||
"""
|
||||
first = User(username="1", password="", access=UserAccess.Read)
|
||||
second = User(username="2", password="", access=UserAccess.Full)
|
||||
third = User(username="3", password="", access=UserAccess.Read)
|
||||
first = User(username="1", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
second = User(username="2", password="", access=UserAccess.Full, packager_id=None, key=None)
|
||||
third = User(username="3", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
|
||||
database.user_update(first)
|
||||
database.user_update(second)
|
||||
@ -45,9 +46,9 @@ def test_user_list_filter_by_access(database: SQLite) -> None:
|
||||
"""
|
||||
must return users filtered by its access
|
||||
"""
|
||||
first = User(username="1", password="", access=UserAccess.Read)
|
||||
second = User(username="2", password="", access=UserAccess.Full)
|
||||
third = User(username="3", password="", access=UserAccess.Read)
|
||||
first = User(username="1", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
second = User(username="2", password="", access=UserAccess.Full, packager_id=None, key=None)
|
||||
third = User(username="3", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
|
||||
database.user_update(first)
|
||||
database.user_update(second)
|
||||
@ -63,9 +64,9 @@ def test_user_list_filter_by_username_access(database: SQLite) -> None:
|
||||
"""
|
||||
must return users filtered by its access and username
|
||||
"""
|
||||
first = User(username="1", password="", access=UserAccess.Read)
|
||||
second = User(username="2", password="", access=UserAccess.Full)
|
||||
third = User(username="3", password="", access=UserAccess.Read)
|
||||
first = User(username="1", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
second = User(username="2", password="", access=UserAccess.Full, packager_id=None, key=None)
|
||||
third = User(username="3", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
|
||||
database.user_update(first)
|
||||
database.user_update(second)
|
||||
@ -91,6 +92,7 @@ def test_user_update(database: SQLite, user: User) -> None:
|
||||
database.user_update(user)
|
||||
assert database.user_get(user.username) == user
|
||||
|
||||
new_user = User(username=user.username, password=user.hash_password("salt").password, access=UserAccess.Full)
|
||||
new_user = User(username=user.username, password=user.hash_password("salt").password, access=UserAccess.Full,
|
||||
packager_id=None, key="new key")
|
||||
database.user_update(new_user)
|
||||
assert database.user_get(new_user.username) == new_user
|
||||
|
@ -32,7 +32,7 @@ def test_package_update(database: SQLite, configuration: Configuration, package_
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_get", return_value=[patch1, patch2])
|
||||
patches_write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||
runner = RemotePush(configuration, database, "gitremote")
|
||||
runner = RemotePush(database, configuration, "gitremote")
|
||||
|
||||
assert runner.package_update(package_ahriman, local) == package_ahriman.base
|
||||
glob_mock.assert_called_once_with(".git*")
|
||||
@ -56,7 +56,7 @@ def test_packages_update(database: SQLite, configuration: Configuration, result:
|
||||
"""
|
||||
update_mock = mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.package_update",
|
||||
return_value=[package_ahriman.base])
|
||||
runner = RemotePush(configuration, database, "gitremote")
|
||||
runner = RemotePush(database, configuration, "gitremote")
|
||||
|
||||
local = Path("local")
|
||||
assert list(runner.packages_update(result, local))
|
||||
@ -71,7 +71,7 @@ def test_run(database: SQLite, configuration: Configuration, result: Result, pac
|
||||
mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.packages_update", return_value=[package_ahriman.base])
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
push_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.push")
|
||||
runner = RemotePush(configuration, database, "gitremote")
|
||||
runner = RemotePush(database, configuration, "gitremote")
|
||||
|
||||
runner.run(result)
|
||||
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source)
|
||||
@ -85,7 +85,7 @@ def test_run_failed(database: SQLite, configuration: Configuration, result: Resu
|
||||
must reraise exception on error occurred
|
||||
"""
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception())
|
||||
runner = RemotePush(configuration, database, "gitremote")
|
||||
runner = RemotePush(database, configuration, "gitremote")
|
||||
|
||||
with pytest.raises(GitRemoteError):
|
||||
runner.run(result)
|
||||
|
@ -6,6 +6,8 @@ from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.repository.executor import Executor
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.user import User
|
||||
|
||||
|
||||
def test_load_archives(executor: Executor) -> None:
|
||||
@ -33,7 +35,7 @@ def test_process_build(executor: Executor, package_ahriman: Package, mocker: Moc
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building")
|
||||
|
||||
executor.process_build([package_ahriman])
|
||||
executor.process_build([package_ahriman], Packagers("packager"))
|
||||
# must move files (once)
|
||||
move_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base)
|
||||
# must update status
|
||||
@ -157,7 +159,7 @@ def test_process_remove_unknown(executor: Executor, package_ahriman: Package, mo
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
def test_process_update(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_process_update(executor: Executor, package_ahriman: Package, user: User, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run update process
|
||||
"""
|
||||
@ -168,14 +170,16 @@ def test_process_update(executor: Executor, package_ahriman: Package, mocker: Mo
|
||||
sign_package_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_package", side_effect=lambda fn, _: [fn])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
packager_mock = mocker.patch("ahriman.core.repository.executor.Executor.packager", return_value=user)
|
||||
filepath = next(package.filepath for package in package_ahriman.packages.values())
|
||||
|
||||
# must return complete
|
||||
assert executor.process_update([filepath])
|
||||
assert executor.process_update([filepath], Packagers("packager"))
|
||||
packager_mock.assert_called_once_with(Packagers("packager"), "ahriman")
|
||||
# must move files (once)
|
||||
move_mock.assert_called_once_with(executor.paths.packages / filepath, executor.paths.repository / filepath)
|
||||
# must sign package
|
||||
sign_package_mock.assert_called_once_with(executor.paths.packages / filepath, package_ahriman.base)
|
||||
sign_package_mock.assert_called_once_with(executor.paths.packages / filepath, user.key)
|
||||
# must add package
|
||||
repo_add_mock.assert_called_once_with(executor.paths.repository / filepath)
|
||||
# must update status
|
||||
|
@ -4,6 +4,10 @@ from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import UnsafeRunError
|
||||
from ahriman.core.repository.repository_properties import RepositoryProperties
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
from ahriman.models.user import User
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
def test_create_tree_on_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None:
|
||||
@ -12,7 +16,8 @@ def test_create_tree_on_load(configuration: Configuration, database: SQLite, moc
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository_properties.check_user")
|
||||
tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
RepositoryProperties("x86_64", configuration, database, report=False, unsafe=False, refresh_pacman_database=0)
|
||||
RepositoryProperties("x86_64", configuration, database, report=False, unsafe=False,
|
||||
refresh_pacman_database=PacmanSynchronization.Disabled)
|
||||
|
||||
tree_create_mock.assert_called_once_with()
|
||||
|
||||
@ -23,6 +28,36 @@ def test_create_tree_on_load_unsafe(configuration: Configuration, database: SQLi
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository_properties.check_user", side_effect=UnsafeRunError(0, 1))
|
||||
tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
RepositoryProperties("x86_64", configuration, database, report=False, unsafe=False, refresh_pacman_database=0)
|
||||
RepositoryProperties("x86_64", configuration, database, report=False, unsafe=False,
|
||||
refresh_pacman_database=PacmanSynchronization.Disabled)
|
||||
|
||||
tree_create_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_packager(repository: RepositoryProperties, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must extract packager
|
||||
"""
|
||||
database_mock = mocker.patch("ahriman.core.database.SQLite.user_get")
|
||||
assert repository.packager(Packagers("username", {}), "base")
|
||||
database_mock.assert_called_once_with("username")
|
||||
|
||||
|
||||
def test_packager_empty(repository: RepositoryProperties, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty user if username was not set
|
||||
"""
|
||||
database_mock = mocker.patch("ahriman.core.database.SQLite.user_get")
|
||||
user = User(username="", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
assert repository.packager(Packagers(), "base") == user
|
||||
database_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_packager_empty_result(repository: RepositoryProperties, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty user if it wasn't found in database
|
||||
"""
|
||||
database_mock = mocker.patch("ahriman.core.database.SQLite.user_get", return_value=None)
|
||||
user = User(username="username", password="", access=UserAccess.Read, packager_id=None, key=None)
|
||||
assert repository.packager(Packagers(user.username), "base") == user
|
||||
database_mock.assert_called_once_with(user.username)
|
||||
|
@ -74,7 +74,7 @@ def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Pack
|
||||
package_load_mock = mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
||||
|
||||
assert update_handler.updates_aur([package_ahriman.base], vcs=True) == [package_ahriman]
|
||||
package_load_mock.assert_called_once_with(package_ahriman.base, update_handler.pacman)
|
||||
package_load_mock.assert_called_once_with(package_ahriman.base, update_handler.pacman, None)
|
||||
|
||||
|
||||
def test_updates_aur_ignore(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
@ -120,7 +120,7 @@ def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
|
||||
assert update_handler.updates_local(vcs=True) == [package_ahriman]
|
||||
fetch_mock.assert_called_once_with(Path(package_ahriman.base), remote=None)
|
||||
package_load_mock.assert_called_once_with(Path(package_ahriman.base), "x86_64")
|
||||
package_load_mock.assert_called_once_with(Path(package_ahriman.base), "x86_64", None)
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
package_is_outdated_mock.assert_called_once_with(
|
||||
package_ahriman, update_handler.paths,
|
||||
|
@ -135,21 +135,6 @@ 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))
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
must call process method correctly
|
||||
@ -170,7 +155,7 @@ def test_process_sign_package_1(gpg_with_key: GPG, mocker: MockerFixture) -> Non
|
||||
|
||||
gpg_with_key.targets = {SignSettings.Packages}
|
||||
assert gpg_with_key.process_sign_package(Path("a"), "a") == result
|
||||
process_mock.assert_called_once_with(Path("a"), "key")
|
||||
process_mock.assert_called_once_with(Path("a"), "a")
|
||||
|
||||
|
||||
def test_process_sign_package_2(gpg_with_key: GPG, mocker: MockerFixture) -> None:
|
||||
@ -182,7 +167,19 @@ def test_process_sign_package_2(gpg_with_key: GPG, mocker: MockerFixture) -> Non
|
||||
|
||||
gpg_with_key.targets = {SignSettings.Packages, SignSettings.Repository}
|
||||
assert gpg_with_key.process_sign_package(Path("a"), "a") == result
|
||||
process_mock.assert_called_once_with(Path("a"), "key")
|
||||
process_mock.assert_called_once_with(Path("a"), "a")
|
||||
|
||||
|
||||
def test_process_sign_package_3(gpg_with_key: GPG, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sign package with default key if none passed
|
||||
"""
|
||||
result = [Path("a"), Path("a.sig")]
|
||||
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process", return_value=result)
|
||||
|
||||
gpg_with_key.targets = {SignSettings.Packages}
|
||||
assert gpg_with_key.process_sign_package(Path("a"), None) == result
|
||||
process_mock.assert_called_once_with(Path("a"), gpg_with_key.default_key)
|
||||
|
||||
|
||||
def test_process_sign_package_skip_1(gpg_with_key: GPG, mocker: MockerFixture) -> None:
|
||||
@ -211,7 +208,7 @@ def test_process_sign_package_skip_3(gpg: GPG, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
|
||||
gpg.targets = {SignSettings.Packages}
|
||||
gpg.process_sign_package(Path("a"), "a")
|
||||
gpg.process_sign_package(Path("a"), None)
|
||||
process_mock.assert_not_called()
|
||||
|
||||
|
||||
@ -221,7 +218,7 @@ def test_process_sign_package_skip_4(gpg: GPG, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
|
||||
gpg.targets = {SignSettings.Packages, SignSettings.Repository}
|
||||
gpg.process_sign_package(Path("a"), "a")
|
||||
gpg.process_sign_package(Path("a"), None)
|
||||
process_mock.assert_not_called()
|
||||
|
||||
|
||||
|
@ -1,24 +1,26 @@
|
||||
import pytest
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
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:
|
||||
def keyring_generator(database: SQLite, gpg: GPG, configuration: Configuration) -> KeyringGenerator:
|
||||
"""
|
||||
fixture for keyring pkgbuild generator
|
||||
|
||||
Args:
|
||||
database(SQLite): database fixture
|
||||
gpg(GPG): empty GPG fixture
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
Returns:
|
||||
KeyringGenerator: keyring generator test instance
|
||||
"""
|
||||
return KeyringGenerator(gpg, configuration, "keyring")
|
||||
return KeyringGenerator(database, gpg, configuration, "keyring")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -5,84 +5,87 @@ from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock, call as MockCall
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import PkgbuildGeneratorError
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
|
||||
from ahriman.models.user import User
|
||||
|
||||
|
||||
def test_init_packagers(gpg: GPG, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_init_packagers(database: SQLite, gpg: GPG, configuration: Configuration, user: User,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must extract packagers keys
|
||||
"""
|
||||
mocker.patch("ahriman.core.sign.gpg.GPG.keys", return_value=["key"])
|
||||
mocker.patch("ahriman.core.database.SQLite.user_list", return_value=[user])
|
||||
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").packagers == ["key"]
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").packagers == ["key"]
|
||||
|
||||
configuration.set_option("keyring", "packagers", "key1")
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").packagers == ["key1"]
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").packagers == ["key1"]
|
||||
|
||||
|
||||
def test_init_revoked(gpg: GPG, configuration: Configuration) -> None:
|
||||
def test_init_revoked(database: SQLite, gpg: GPG, configuration: Configuration) -> None:
|
||||
"""
|
||||
must extract revoked keys
|
||||
"""
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").revoked == []
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").revoked == []
|
||||
|
||||
configuration.set_option("keyring", "revoked", "key1")
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").revoked == ["key1"]
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").revoked == ["key1"]
|
||||
|
||||
|
||||
def test_init_trusted(gpg: GPG, configuration: Configuration) -> None:
|
||||
def test_init_trusted(database: SQLite, gpg: GPG, configuration: Configuration) -> None:
|
||||
"""
|
||||
must extract trusted keys
|
||||
"""
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").trusted == []
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").trusted == []
|
||||
|
||||
gpg.default_key = "key"
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").trusted == ["key"]
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").trusted == ["key"]
|
||||
|
||||
configuration.set_option("keyring", "trusted", "key1")
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").trusted == ["key1"]
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").trusted == ["key1"]
|
||||
|
||||
|
||||
def test_license(gpg: GPG, configuration: Configuration) -> None:
|
||||
def test_license(database: SQLite, gpg: GPG, configuration: Configuration) -> None:
|
||||
"""
|
||||
must generate correct licenses list
|
||||
"""
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").license == ["Unlicense"]
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").license == ["Unlicense"]
|
||||
|
||||
configuration.set_option("keyring", "license", "GPL MPL")
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").license == ["GPL", "MPL"]
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").license == ["GPL", "MPL"]
|
||||
|
||||
|
||||
def test_pkgdesc(gpg: GPG, configuration: Configuration) -> None:
|
||||
def test_pkgdesc(database: SQLite, gpg: GPG, configuration: Configuration) -> None:
|
||||
"""
|
||||
must generate correct pkgdesc property
|
||||
"""
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").pkgdesc == "aur-clone PGP keyring"
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").pkgdesc == "aur-clone PGP keyring"
|
||||
|
||||
configuration.set_option("keyring", "description", "description")
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").pkgdesc == "description"
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").pkgdesc == "description"
|
||||
|
||||
|
||||
def test_pkgname(gpg: GPG, configuration: Configuration) -> None:
|
||||
def test_pkgname(database: SQLite, gpg: GPG, configuration: Configuration) -> None:
|
||||
"""
|
||||
must generate correct pkgname property
|
||||
"""
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").pkgname == "aur-clone-keyring"
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").pkgname == "aur-clone-keyring"
|
||||
|
||||
configuration.set_option("keyring", "package", "keyring")
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").pkgname == "keyring"
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").pkgname == "keyring"
|
||||
|
||||
|
||||
def test_url(gpg: GPG, configuration: Configuration) -> None:
|
||||
def test_url(database: SQLite, gpg: GPG, configuration: Configuration) -> None:
|
||||
"""
|
||||
must generate correct url property
|
||||
"""
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").url == ""
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").url == ""
|
||||
|
||||
configuration.set_option("keyring", "homepage", "homepage")
|
||||
assert KeyringGenerator(gpg, configuration, "keyring").url == "homepage"
|
||||
assert KeyringGenerator(database, gpg, configuration, "keyring").url == "homepage"
|
||||
|
||||
|
||||
def test_generate_gpg(keyring_generator: KeyringGenerator, mocker: MockerFixture) -> None:
|
||||
|
@ -1,6 +1,8 @@
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.support import KeyringTrigger
|
||||
from ahriman.models.context_key import ContextKey
|
||||
@ -21,10 +23,10 @@ def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run report for specified targets
|
||||
"""
|
||||
gpg_mock = mocker.patch("ahriman.core._Context.get")
|
||||
context_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))
|
||||
context_mock.assert_has_calls([MockCall(ContextKey("sign", GPG)), MockCall(ContextKey("database", SQLite))])
|
||||
run_mock.assert_called_once_with()
|
||||
|
@ -35,6 +35,6 @@ def test_run(package_creator: PackageCreator, database: SQLite, mocker: MockerFi
|
||||
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")
|
||||
package_mock.assert_called_once_with(local_path, "x86_64", None)
|
||||
database_mock.assert_called_once_with(ContextKey("database", SQLite))
|
||||
insert_mock.assert_called_once_with(package, pytest.helpers.anyvar(int))
|
||||
|
@ -42,7 +42,7 @@ def test_spawn_process(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
start_mock = mocker.patch("multiprocessing.Process.start")
|
||||
|
||||
spawner._spawn_process("add", "ahriman", now="", maybe="?")
|
||||
spawner._spawn_process("add", "ahriman", now="", maybe="?", none=None)
|
||||
start_mock.assert_called_once_with()
|
||||
spawner.args_parser.parse_args.assert_called_once_with(
|
||||
spawner.command_arguments + [
|
||||
@ -74,8 +74,8 @@ def test_packages_add(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
must call package addition
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
|
||||
spawner.packages_add(["ahriman", "linux"], now=False)
|
||||
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur")
|
||||
spawner.packages_add(["ahriman", "linux"], None, now=False)
|
||||
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", username=None)
|
||||
|
||||
|
||||
def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
@ -83,8 +83,17 @@ def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
must call package addition with update
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
|
||||
spawner.packages_add(["ahriman", "linux"], now=True)
|
||||
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", now="")
|
||||
spawner.packages_add(["ahriman", "linux"], None, now=True)
|
||||
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", username=None, now="")
|
||||
|
||||
|
||||
def test_packages_add_with_username(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call package addition with username
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
|
||||
spawner.packages_add(["ahriman", "linux"], "username", now=False)
|
||||
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", username="username")
|
||||
|
||||
|
||||
def test_packages_rebuild(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
@ -92,8 +101,8 @@ def test_packages_rebuild(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
must call package rebuild
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
|
||||
spawner.packages_rebuild("python")
|
||||
spawn_mock.assert_called_once_with("repo-rebuild", **{"depends-on": "python"})
|
||||
spawner.packages_rebuild("python", "packager")
|
||||
spawn_mock.assert_called_once_with("repo-rebuild", **{"depends-on": "python", "username": "packager"})
|
||||
|
||||
|
||||
def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
@ -110,8 +119,8 @@ def test_packages_update(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
must call repo update
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
|
||||
spawner.packages_update()
|
||||
spawn_mock.assert_called_once_with("repo-update")
|
||||
spawner.packages_update("packager")
|
||||
spawn_mock.assert_called_once_with("repo-update", username="packager")
|
||||
|
||||
|
||||
def test_run(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
|
@ -11,9 +11,9 @@ from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.core.exceptions import BuildError, OptionError, UnsafeRunError
|
||||
from ahriman.core.util import check_output, check_user, enum_values, exception_response_text, filter_json, \
|
||||
full_version, package_like, partition, pretty_datetime, pretty_size, safe_filename, srcinfo_property, \
|
||||
srcinfo_property_list, trim_package, utcnow, walk
|
||||
from ahriman.core.util import check_output, check_user, dataclass_view, enum_values, exception_response_text,\
|
||||
extract_user, filter_json, full_version, package_like, partition, pretty_datetime, pretty_size, safe_filename, \
|
||||
srcinfo_property, srcinfo_property_list, trim_package, utcnow, walk
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@ -91,6 +91,16 @@ def test_check_output_with_user(passwd: Any, mocker: MockerFixture) -> None:
|
||||
getpwuid_mock.assert_called_once_with(user)
|
||||
|
||||
|
||||
def test_check_output_with_user_and_environment(passwd: Any, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run set environment if both environment and user are set
|
||||
"""
|
||||
mocker.patch("ahriman.core.util.getpwuid", return_value=passwd)
|
||||
user = os.getuid()
|
||||
assert check_output("python", "-c", """import os; print(os.getenv("HOME"), os.getenv("VAR"))""",
|
||||
environment={"VAR": "VALUE"}, user=user) == f"{passwd.pw_dir} VALUE"
|
||||
|
||||
|
||||
def test_check_output_failure(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process exception correctly
|
||||
@ -155,6 +165,23 @@ def test_check_user_unsafe(mocker: MockerFixture) -> None:
|
||||
check_user(paths, unsafe=True)
|
||||
|
||||
|
||||
def test_dataclass_view(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must serialize dataclasses
|
||||
"""
|
||||
assert Package.from_json(dataclass_view(package_ahriman)) == package_ahriman
|
||||
|
||||
|
||||
def test_dataclass_view_without_none(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must serialize dataclasses with None fields removed
|
||||
"""
|
||||
package_ahriman.packager = None
|
||||
result = dataclass_view(package_ahriman)
|
||||
assert "packager" not in result
|
||||
assert Package.from_json(result) == package_ahriman
|
||||
|
||||
|
||||
def test_exception_response_text() -> None:
|
||||
"""
|
||||
must parse HTTP response to string
|
||||
@ -174,6 +201,23 @@ def test_exception_response_text_empty() -> None:
|
||||
assert exception_response_text(exception) == ""
|
||||
|
||||
|
||||
def test_extract_user() -> None:
|
||||
"""
|
||||
must extract user from system environment
|
||||
"""
|
||||
os.environ["USER"] = "user"
|
||||
assert extract_user() == "user"
|
||||
|
||||
os.environ["SUDO_USER"] = "sudo"
|
||||
assert extract_user() == "sudo"
|
||||
|
||||
os.environ["DOAS_USER"] = "doas"
|
||||
assert extract_user() == "sudo"
|
||||
|
||||
del os.environ["SUDO_USER"]
|
||||
assert extract_user() == "doas"
|
||||
|
||||
|
||||
def test_filter_json(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must filter fields by known list
|
||||
|
@ -116,6 +116,7 @@ def pyalpm_package_ahriman(aur_package_ahriman: AURPackage) -> MagicMock:
|
||||
type(mock).name = PropertyMock(return_value=aur_package_ahriman.name)
|
||||
type(mock).optdepends = PropertyMock(return_value=aur_package_ahriman.opt_depends)
|
||||
type(mock).checkdepends = PropertyMock(return_value=aur_package_ahriman.check_depends)
|
||||
type(mock).packager = PropertyMock(return_value="packager")
|
||||
type(mock).provides = PropertyMock(return_value=aur_package_ahriman.provides)
|
||||
type(mock).version = PropertyMock(return_value=aur_package_ahriman.version)
|
||||
type(mock).url = PropertyMock(return_value=aur_package_ahriman.url)
|
||||
|
@ -56,7 +56,7 @@ def test_depends_build_with_version_and_overlap(mocker: MockerFixture, resource_
|
||||
srcinfo = (resource_path_root / "models" / "package_gcc10_srcinfo").read_text()
|
||||
mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
|
||||
|
||||
package_gcc10 = Package.from_build(Path("local"), "x86_64")
|
||||
package_gcc10 = Package.from_build(Path("local"), "x86_64", None)
|
||||
assert package_gcc10.depends_build == {
|
||||
"glibc", "zstd", # depends
|
||||
"doxygen", "binutils", "git", "libmpc", "python", # make depends
|
||||
@ -168,10 +168,11 @@ def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, pac
|
||||
"""
|
||||
mocker.patch("ahriman.core.alpm.remote.AUR.info", return_value=aur_package_ahriman)
|
||||
|
||||
package = Package.from_aur(package_ahriman.base, pacman)
|
||||
package = Package.from_aur(package_ahriman.base, pacman, package_ahriman.packager)
|
||||
assert package_ahriman.base == package.base
|
||||
assert package_ahriman.version == package.version
|
||||
assert package_ahriman.packages.keys() == package.packages.keys()
|
||||
assert package_ahriman.packager == package.packager
|
||||
|
||||
|
||||
def test_from_build(package_ahriman: Package, mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
@ -181,7 +182,7 @@ def test_from_build(package_ahriman: Package, mocker: MockerFixture, resource_pa
|
||||
srcinfo = (resource_path_root / "models" / "package_ahriman_srcinfo").read_text()
|
||||
mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
|
||||
|
||||
package = Package.from_build(Path("path"), "x86_64")
|
||||
package = Package.from_build(Path("path"), "x86_64", "packager")
|
||||
assert package_ahriman.packages.keys() == package.packages.keys()
|
||||
package_ahriman.packages = package.packages # we are not going to test PackageDescription here
|
||||
package_ahriman.remote = package.remote
|
||||
@ -195,7 +196,7 @@ def test_from_build_multiple_packages(mocker: MockerFixture, resource_path_root:
|
||||
srcinfo = (resource_path_root / "models" / "package_gcc10_srcinfo").read_text()
|
||||
mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
|
||||
|
||||
package = Package.from_build(Path("path"), "x86_64")
|
||||
package = Package.from_build(Path("path"), "x86_64", None)
|
||||
assert package.packages == {
|
||||
"gcc10": PackageDescription(
|
||||
depends=["gcc10-libs=10.3.0-2", "binutils>=2.28", "libmpc", "zstd"],
|
||||
@ -225,7 +226,7 @@ def test_from_build_architecture(mocker: MockerFixture, resource_path_root: Path
|
||||
srcinfo = (resource_path_root / "models" / "package_jellyfin-ffmpeg5-bin_srcinfo").read_text()
|
||||
mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
|
||||
|
||||
package = Package.from_build(Path("path"), "x86_64")
|
||||
package = Package.from_build(Path("path"), "x86_64", None)
|
||||
assert package.packages == {
|
||||
"jellyfin-ffmpeg5-bin": PackageDescription(
|
||||
depends=["glibc"],
|
||||
@ -254,7 +255,7 @@ def test_from_build_failed(package_ahriman: Package, mocker: MockerFixture) -> N
|
||||
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
|
||||
|
||||
with pytest.raises(PackageInfoError):
|
||||
Package.from_build(Path("path"), "x86_64")
|
||||
Package.from_build(Path("path"), "x86_64", None)
|
||||
|
||||
|
||||
def test_from_json_view_1(package_ahriman: Package) -> None:
|
||||
@ -285,10 +286,11 @@ def test_from_official(package_ahriman: Package, aur_package_ahriman: AURPackage
|
||||
"""
|
||||
mocker.patch("ahriman.core.alpm.remote.Official.info", return_value=aur_package_ahriman)
|
||||
|
||||
package = Package.from_official(package_ahriman.base, pacman)
|
||||
package = Package.from_official(package_ahriman.base, pacman, package_ahriman.packager)
|
||||
assert package_ahriman.base == package.base
|
||||
assert package_ahriman.version == package.version
|
||||
assert package_ahriman.packages.keys() == package.packages.keys()
|
||||
assert package_ahriman.packager == package.packager
|
||||
|
||||
|
||||
def test_local_files(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
|
12
tests/ahriman/models/test_packagers.py
Normal file
12
tests/ahriman/models/test_packagers.py
Normal file
@ -0,0 +1,12 @@
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.packagers import Packagers
|
||||
|
||||
|
||||
def test_for_base(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return username used for base package
|
||||
"""
|
||||
assert Packagers(None, {package_ahriman.base: "packager"}).for_base(package_ahriman.base) == "packager"
|
||||
assert Packagers("default", {package_ahriman.base: "packager"}).for_base("random") == "default"
|
||||
assert Packagers("default").for_base(package_ahriman.base) == "default"
|
||||
assert Packagers().for_base(package_ahriman.base) is None
|
@ -8,7 +8,7 @@ def test_from_option(user: User) -> None:
|
||||
"""
|
||||
must generate user from options
|
||||
"""
|
||||
user = replace(user, access=UserAccess.Read)
|
||||
user = replace(user, access=UserAccess.Read, packager_id=None, key=None)
|
||||
assert User.from_option(user.username, user.password) == user
|
||||
# default is read access
|
||||
user = replace(user, access=UserAccess.Full)
|
||||
|
@ -2,6 +2,7 @@ import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.service.add import AddView
|
||||
@ -21,13 +22,16 @@ async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
must call post request correctly
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_add")
|
||||
user_mock = AsyncMock()
|
||||
user_mock.return_value = "username"
|
||||
mocker.patch("ahriman.web.views.base.BaseView.username", side_effect=user_mock)
|
||||
request_schema = pytest.helpers.schema_request(AddView.post)
|
||||
|
||||
payload = {"packages": ["ahriman"]}
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client.post("/api/v1/service/add", json=payload)
|
||||
assert response.ok
|
||||
add_mock.assert_called_once_with(["ahriman"], now=True)
|
||||
add_mock.assert_called_once_with(["ahriman"], "username", now=True)
|
||||
|
||||
|
||||
async def test_post_empty(client: TestClient, mocker: MockerFixture) -> None:
|
||||
|
@ -2,6 +2,7 @@ import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.service.rebuild import RebuildView
|
||||
@ -21,13 +22,16 @@ async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
must call post request correctly
|
||||
"""
|
||||
rebuild_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_rebuild")
|
||||
user_mock = AsyncMock()
|
||||
user_mock.return_value = "username"
|
||||
mocker.patch("ahriman.web.views.base.BaseView.username", side_effect=user_mock)
|
||||
request_schema = pytest.helpers.schema_request(RebuildView.post)
|
||||
|
||||
payload = {"packages": ["python", "ahriman"]}
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client.post("/api/v1/service/rebuild", json=payload)
|
||||
assert response.ok
|
||||
rebuild_mock.assert_called_once_with("python")
|
||||
rebuild_mock.assert_called_once_with("python", "username")
|
||||
|
||||
|
||||
async def test_post_exception(client: TestClient, mocker: MockerFixture) -> None:
|
||||
|
@ -2,6 +2,7 @@ import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.service.request import RequestView
|
||||
@ -21,13 +22,16 @@ async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
must call post request correctly
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_add")
|
||||
user_mock = AsyncMock()
|
||||
user_mock.return_value = "username"
|
||||
mocker.patch("ahriman.web.views.base.BaseView.username", side_effect=user_mock)
|
||||
request_schema = pytest.helpers.schema_request(RequestView.post)
|
||||
|
||||
payload = {"packages": ["ahriman"]}
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client.post("/api/v1/service/request", json=payload)
|
||||
assert response.ok
|
||||
add_mock.assert_called_once_with(["ahriman"], now=False)
|
||||
add_mock.assert_called_once_with(["ahriman"], "username", now=False)
|
||||
|
||||
|
||||
async def test_post_exception(client: TestClient, mocker: MockerFixture) -> None:
|
||||
|
@ -1,5 +1,6 @@
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
|
||||
async def test_post_update(client: TestClient, mocker: MockerFixture) -> None:
|
||||
@ -7,7 +8,10 @@ async def test_post_update(client: TestClient, mocker: MockerFixture) -> None:
|
||||
must call post request correctly for alias
|
||||
"""
|
||||
update_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_update")
|
||||
user_mock = AsyncMock()
|
||||
user_mock.return_value = "username"
|
||||
mocker.patch("ahriman.web.views.base.BaseView.username", side_effect=user_mock)
|
||||
|
||||
response = await client.post("/api/v1/service/update")
|
||||
assert response.ok
|
||||
update_mock.assert_called_once_with()
|
||||
update_mock.assert_called_once_with("username")
|
||||
|
@ -2,6 +2,8 @@ import pytest
|
||||
|
||||
from multidict import MultiDict
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
@ -146,3 +148,22 @@ async def test_head_not_allowed(client: TestClient) -> None:
|
||||
"""
|
||||
response = await client.head("/api/v1/service/add")
|
||||
assert response.status == 405
|
||||
|
||||
|
||||
async def test_username(base: BaseView, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return identity of logged-in user
|
||||
"""
|
||||
policy = AsyncMock()
|
||||
policy.identify.return_value = "identity"
|
||||
mocker.patch("aiohttp.web.Application.get", return_value=policy)
|
||||
|
||||
assert await base.username() == "identity"
|
||||
policy.identify.assert_called_once_with(base.request)
|
||||
|
||||
|
||||
async def test_username_no_auth(base: BaseView) -> None:
|
||||
"""
|
||||
must return None in case if auth is disabled
|
||||
"""
|
||||
assert await base.username() is None
|
||||
|
Loading…
Reference in New Issue
Block a user