mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-09 12:05:47 +00:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
db195391e4 | |||
59f2992559 | |||
4f06647193 | |||
73a4cee257 | |||
13d00c6f66 | |||
3e032c3515 | |||
d73d5daad3 | |||
f55b44b391 | |||
51b28baf40 | |||
24326f9753 | |||
36c763069d | |||
c9a155bbc4 | |||
182bde5e09 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -94,5 +94,3 @@ ENV/
|
|||||||
.venv/
|
.venv/
|
||||||
|
|
||||||
*.tar.xz
|
*.tar.xz
|
||||||
|
|
||||||
man/
|
|
||||||
|
9
Makefile
9
Makefile
@ -1,4 +1,4 @@
|
|||||||
.PHONY: architecture archive archive_directory archlinux check clean directory push tests version
|
.PHONY: architecture archive archive_directory archlinux check clean directory man push tests version
|
||||||
.DEFAULT_GOAL := archlinux
|
.DEFAULT_GOAL := archlinux
|
||||||
|
|
||||||
PROJECT := ahriman
|
PROJECT := ahriman
|
||||||
@ -39,12 +39,15 @@ clean:
|
|||||||
directory: clean
|
directory: clean
|
||||||
mkdir "$(PROJECT)"
|
mkdir "$(PROJECT)"
|
||||||
|
|
||||||
|
man:
|
||||||
|
cd src && PYTHONPATH=. argparse-manpage --module ahriman.application.ahriman --function _parser --author "ahriman team" --project-name ahriman --author-email "" --url https://github.com/arcan1s/ahriman --output ../docs/ahriman.1
|
||||||
|
|
||||||
mypy:
|
mypy:
|
||||||
cd src && mypy --implicit-reexport --strict -p "$(PROJECT)" --install-types --non-interactive || true
|
cd src && mypy --implicit-reexport --strict -p "$(PROJECT)" --install-types --non-interactive || true
|
||||||
cd src && mypy --implicit-reexport --strict -p "$(PROJECT)"
|
cd src && mypy --implicit-reexport --strict -p "$(PROJECT)"
|
||||||
|
|
||||||
push: archlinux
|
push: architecture man archlinux
|
||||||
git add package/archlinux/PKGBUILD src/ahriman/version.py
|
git add package/archlinux/PKGBUILD src/ahriman/version.py docs/ahriman-architecture.svg docs/ahriman.1
|
||||||
git commit -m "Release $(VERSION)"
|
git commit -m "Release $(VERSION)"
|
||||||
git tag "$(VERSION)"
|
git tag "$(VERSION)"
|
||||||
git push
|
git push
|
||||||
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 326 KiB |
401
docs/ahriman.1
Normal file
401
docs/ahriman.1
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
.TH ahriman "1" Manual
|
||||||
|
.SH NAME
|
||||||
|
ahriman
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B ahriman
|
||||||
|
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-log] [--no-report] [--unsafe] [-v] {add,check,clean,config,init,key-import,rebuild,remove,remove-unknown,report,search,setup,sign,status,status-update,sync,update,user,web} ...
|
||||||
|
.SH DESCRIPTION
|
||||||
|
ArcH Linux ReposItory MANager
|
||||||
|
.SH OPTIONS
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-a\fR \fI\,ARCHITECTURE\/\fR, \fB\-\-architecture\fR \fI\,ARCHITECTURE\/\fR
|
||||||
|
target architectures (can be used multiple times)
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-c\fR \fI\,CONFIGURATION\/\fR, \fB\-\-configuration\fR \fI\,CONFIGURATION\/\fR
|
||||||
|
configuration path
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-force\fR
|
||||||
|
force run, remove file lock
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-l\fR \fI\,LOCK\/\fR, \fB\-\-lock\fR \fI\,LOCK\/\fR
|
||||||
|
lock file
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-log\fR
|
||||||
|
redirect all log messages to stderr
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-report\fR
|
||||||
|
force disable reporting to web service
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-unsafe\fR
|
||||||
|
allow to run ahriman as non\-ahriman user
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-v\fR, \fB\-\-version\fR
|
||||||
|
show program's version number and exit
|
||||||
|
|
||||||
|
.SS
|
||||||
|
\fBSub-commands\fR
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,add\/\fR
|
||||||
|
add package
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,check\/\fR
|
||||||
|
check for updates
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,clean\/\fR
|
||||||
|
clean local caches
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,config\/\fR
|
||||||
|
dump configuration
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,init\/\fR
|
||||||
|
create repository tree
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,key-import\/\fR
|
||||||
|
import PGP key
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,rebuild\/\fR
|
||||||
|
rebuild repository
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,remove\/\fR
|
||||||
|
remove package
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,remove-unknown\/\fR
|
||||||
|
remove unknown packages
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,report\/\fR
|
||||||
|
generate report
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,search\/\fR
|
||||||
|
search for package
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,setup\/\fR
|
||||||
|
initial service configuration
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,sign\/\fR
|
||||||
|
sign packages
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,status\/\fR
|
||||||
|
get package status
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,status-update\/\fR
|
||||||
|
update package status
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,sync\/\fR
|
||||||
|
sync repository
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,update\/\fR
|
||||||
|
update packages
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,user\/\fR
|
||||||
|
manage users for web services
|
||||||
|
.TP
|
||||||
|
\fBahriman\fR \fI\,web\/\fR
|
||||||
|
start web server
|
||||||
|
.SH OPTIONS 'ahriman add'
|
||||||
|
usage: ahriman add [-h] [--now] [--source {PackageSource.Auto,PackageSource.Archive,PackageSource.Directory,PackageSource.AUR}] [--without-dependencies] package [package ...]
|
||||||
|
|
||||||
|
add package
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBpackage\fR
|
||||||
|
package base/name or archive path
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-now\fR
|
||||||
|
run update function after
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.Directory,PackageSource.AUR}
|
||||||
|
package source
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-without\-dependencies\fR
|
||||||
|
do not add dependencies
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman check'
|
||||||
|
usage: ahriman check [-h] [--no-vcs] [package ...]
|
||||||
|
|
||||||
|
check for updates. Same as update \-\-dry\-run \-\-no\-manual
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBpackage\fR
|
||||||
|
filter check by package base
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-vcs\fR
|
||||||
|
do not check VCS packages
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman clean'
|
||||||
|
usage: ahriman clean [-h] [--no-build] [--no-cache] [--no-chroot] [--no-manual] [--no-packages]
|
||||||
|
|
||||||
|
clear local caches
|
||||||
|
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-build\fR
|
||||||
|
do not clear directory with package sources
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-cache\fR
|
||||||
|
do not clear directory with package caches
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-chroot\fR
|
||||||
|
do not clear build chroot
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-manual\fR
|
||||||
|
do not clear directory with manually added packages
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-packages\fR
|
||||||
|
do not clear directory with built packages
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman config'
|
||||||
|
usage: ahriman config [-h]
|
||||||
|
|
||||||
|
dump configuration for specified architecture
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman init'
|
||||||
|
usage: ahriman init [-h]
|
||||||
|
|
||||||
|
create empty repository tree. Optional command for auto architecture support
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman key-import'
|
||||||
|
usage: ahriman key-import [-h] [--key-server KEY_SERVER] key
|
||||||
|
|
||||||
|
import PGP key from public sources to repository user
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBkey\fR
|
||||||
|
PGP key to import from public server
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-key\-server\fR \fI\,KEY_SERVER\/\fR
|
||||||
|
key server for key import
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman rebuild'
|
||||||
|
usage: ahriman rebuild [-h] [--depends-on DEPENDS_ON]
|
||||||
|
|
||||||
|
rebuild whole repository
|
||||||
|
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-depends\-on\fR \fI\,DEPENDS_ON\/\fR
|
||||||
|
only rebuild packages that depend on specified package
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman remove'
|
||||||
|
usage: ahriman remove [-h] package [package ...]
|
||||||
|
|
||||||
|
remove package
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBpackage\fR
|
||||||
|
package name or base
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman remove-unknown'
|
||||||
|
usage: ahriman remove-unknown [-h] [--dry-run]
|
||||||
|
|
||||||
|
remove packages which are missing in AUR
|
||||||
|
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-dry\-run\fR
|
||||||
|
just perform check for packages without removal
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman report'
|
||||||
|
usage: ahriman report [-h] [target ...]
|
||||||
|
|
||||||
|
generate report
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBtarget\fR
|
||||||
|
target to generate report
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman search'
|
||||||
|
usage: ahriman search [-h] search [search ...]
|
||||||
|
|
||||||
|
search for package in AUR using API
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBsearch\fR
|
||||||
|
search terms, can be specified multiple times
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman setup'
|
||||||
|
usage: ahriman setup [-h] [--build-command BUILD_COMMAND] [--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository REPOSITORY [--sign-key SIGN_KEY]
|
||||||
|
[--sign-target {SignSettings.Packages,SignSettings.Repository}] [--web-port WEB_PORT]
|
||||||
|
|
||||||
|
create initial service configuration, requires root
|
||||||
|
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-build\-command\fR \fI\,BUILD_COMMAND\/\fR
|
||||||
|
build command prefix
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-from\-configuration\fR \fI\,FROM_CONFIGURATION\/\fR
|
||||||
|
path to default devtools pacman configuration
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-multilib\fR
|
||||||
|
do not add multilib repository
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-packager\fR \fI\,PACKAGER\/\fR
|
||||||
|
packager name and email
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-repository\fR \fI\,REPOSITORY\/\fR
|
||||||
|
repository name
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-sign\-key\fR \fI\,SIGN_KEY\/\fR
|
||||||
|
sign key id
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-sign\-target\fR {SignSettings.Packages,SignSettings.Repository}
|
||||||
|
sign options
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-web\-port\fR \fI\,WEB_PORT\/\fR
|
||||||
|
port of the web service
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman sign'
|
||||||
|
usage: ahriman sign [-h] [package ...]
|
||||||
|
|
||||||
|
(re\-)sign packages and repository database
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBpackage\fR
|
||||||
|
sign only specified packages
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman status'
|
||||||
|
usage: ahriman status [-h] [--ahriman] [--status {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}] [package ...]
|
||||||
|
|
||||||
|
request status of the package
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBpackage\fR
|
||||||
|
filter status by package base
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-ahriman\fR
|
||||||
|
get service status itself
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
|
||||||
|
filter packages by status
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman status-update'
|
||||||
|
usage: ahriman status-update [-h] [--status {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}] [--remove] [package ...]
|
||||||
|
|
||||||
|
request status of the package
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBpackage\fR
|
||||||
|
set status for specified packages. If no packages supplied, service status will be updated
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
|
||||||
|
new status
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-remove\fR
|
||||||
|
remove package status page
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman sync'
|
||||||
|
usage: ahriman sync [-h] [target ...]
|
||||||
|
|
||||||
|
sync packages to remote server
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBtarget\fR
|
||||||
|
target to sync
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman update'
|
||||||
|
usage: ahriman update [-h] [--dry-run] [--no-aur] [--no-manual] [--no-vcs] [package ...]
|
||||||
|
|
||||||
|
run updates
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBpackage\fR
|
||||||
|
filter check by package base
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-dry\-run\fR
|
||||||
|
just perform check for updates, same as check command
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-aur\fR
|
||||||
|
do not check for AUR updates. Implies \-\-no\-vcs
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-manual\fR
|
||||||
|
do not include manual updates
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-vcs\fR
|
||||||
|
do not check VCS packages
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman user'
|
||||||
|
usage: ahriman user [-h] [--as-service] [-a {UserAccess.Safe,UserAccess.Read,UserAccess.Write}] [--no-reload] [-p PASSWORD] [-r] [--secure] username
|
||||||
|
|
||||||
|
manage users for web services with password and role. In case if password was not entered it will be asked interactively
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fBusername\fR
|
||||||
|
username for web service
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-as\-service\fR
|
||||||
|
add user as service user
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-a\fR {UserAccess.Safe,UserAccess.Read,UserAccess.Write}, \fB\-\-access\fR {UserAccess.Safe,UserAccess.Read,UserAccess.Write}
|
||||||
|
user access level
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-reload\fR
|
||||||
|
do not reload authentication module
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-p\fR \fI\,PASSWORD\/\fR, \fB\-\-password\fR \fI\,PASSWORD\/\fR
|
||||||
|
user password
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-r\fR, \fB\-\-remove\fR
|
||||||
|
remove user from configuration
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-secure\fR
|
||||||
|
set file permissions to user\-only
|
||||||
|
|
||||||
|
.SH OPTIONS 'ahriman web'
|
||||||
|
usage: ahriman web [-h]
|
||||||
|
|
||||||
|
start web server
|
||||||
|
|
||||||
|
.SH AUTHORS
|
||||||
|
.B ahriman
|
||||||
|
was written by ahriman team <>.
|
||||||
|
.SH DISTRIBUTION
|
||||||
|
The latest version of ahriman may be downloaded from
|
||||||
|
.UR https://github.com/arcan1s/ahriman
|
||||||
|
.UE
|
@ -148,6 +148,7 @@ Some features require optional dependencies to be installed:
|
|||||||
Web application requires the following python packages to be installed:
|
Web application requires the following python packages to be installed:
|
||||||
|
|
||||||
* Core part requires `aiohttp` (application itself), `aiohttp_jinja2` and `Jinja2` (HTML generation from templates).
|
* Core part requires `aiohttp` (application itself), `aiohttp_jinja2` and `Jinja2` (HTML generation from templates).
|
||||||
|
* In addition, `aiohttp_debugtoolbar` is required for debug panel. Please note that this option does not work together with authorization and basically must not be used in production.
|
||||||
* In addition, authorization feature requires `aiohttp_security`, `aiohttp_session` and `cryptography`.
|
* In addition, authorization feature requires `aiohttp_security`, `aiohttp_session` and `cryptography`.
|
||||||
* In addition to base authorization dependencies, OAuth2 also requires `aioauth-client` library.
|
* In addition to base authorization dependencies, OAuth2 also requires `aioauth-client` library.
|
||||||
|
|
||||||
@ -173,9 +174,9 @@ Package provides base jinja templates which can be overridden by settings. Vanil
|
|||||||
|
|
||||||
## Requests and scopes
|
## Requests and scopes
|
||||||
|
|
||||||
Service provides optional authorization which can be turned on in settings. In order to control user access there are two levels of authorization - read-only (only GET-like requests) and write (anything).
|
Service provides optional authorization which can be turned on in settings. In order to control user access there are two levels of authorization - read-only (only GET-like requests) and write (anything) which are provided by each web view directly.
|
||||||
|
|
||||||
If this feature is configured any request except for whitelisted will be prohibited without authentication. In addition, configuration flag `auth.allow_read_only` can be used in order to allow seeing main page without authorization (this page is in default white list).
|
If this feature is configured any request will be prohibited without authentication. In addition, configuration flag `auth.safe_build_status` can be used in order to allow seeing main page without authorization.
|
||||||
|
|
||||||
For authenticated users it uses encrypted session cookies to store tokens; encryption key is generated each time at the start of the application. It also stores expiration time of the session inside.
|
For authenticated users it uses encrypted session cookies to store tokens; encryption key is generated each time at the start of the application. It also stores expiration time of the session inside.
|
||||||
|
|
||||||
|
@ -23,14 +23,12 @@ libalpm and AUR related configuration.
|
|||||||
Base authorization settings. `OAuth` provider requires `aioauth-client` library to be installed.
|
Base authorization settings. `OAuth` provider requires `aioauth-client` library to be installed.
|
||||||
|
|
||||||
* `target` - specifies authorization provider, string, optional, default `disabled`. Allowed values are `disabled`, `configuration`, `oauth`.
|
* `target` - specifies authorization provider, string, optional, default `disabled`. Allowed values are `disabled`, `configuration`, `oauth`.
|
||||||
* `allow_read_only` - allow requesting read only pages without authorization, boolean, required.
|
|
||||||
* `allowed_paths` - URI paths (exact match) which can be accessed without authorization, space separated list of strings, optional.
|
|
||||||
* `allowed_paths_groups` - URI paths prefixes which can be accessed without authorization, space separated list of strings, optional.
|
|
||||||
* `client_id` - OAuth2 application client ID, string, required in case if `oauth` is used.
|
* `client_id` - OAuth2 application client ID, string, required in case if `oauth` is used.
|
||||||
* `client_secret` - OAuth2 application client secret key, string, required in case if `oauth` is used.
|
* `client_secret` - OAuth2 application client secret key, string, required in case if `oauth` is used.
|
||||||
* `max_age` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
|
* `max_age` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
|
||||||
* `oauth_provider` - OAuth2 provider class name as is in `aioauth-client` (e.g. `GoogleClient`, `GithubClient` etc), string, required in case if `oauth` is used.
|
* `oauth_provider` - OAuth2 provider class name as is in `aioauth-client` (e.g. `GoogleClient`, `GithubClient` etc), string, required in case if `oauth` is used.
|
||||||
* `oauth_scopes` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. `https://www.googleapis.com/auth/userinfo.email` for `GoogleClient` or `user:email` for `GithubClient`, space separated list of strings, required in case if `oauth` is used.
|
* `oauth_scopes` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. `https://www.googleapis.com/auth/userinfo.email` for `GoogleClient` or `user:email` for `GithubClient`, space separated list of strings, required in case if `oauth` is used.
|
||||||
|
* `safe_build_status` - allow requesting status page without authorization, boolean, required.
|
||||||
* `salt` - password hash salt, string, required in case if authorization enabled (automatically generated by `create-user` subcommand).
|
* `salt` - password hash salt, string, required in case if authorization enabled (automatically generated by `create-user` subcommand).
|
||||||
|
|
||||||
## `auth:*` groups
|
## `auth:*` groups
|
||||||
@ -127,7 +125,11 @@ Group name must refer to architecture, e.g. it should be `s3:x86_64` for x86_64
|
|||||||
Web server settings. If any of `host`/`port` is not set, web integration will be disabled. Group name must refer to architecture, e.g. it should be `web:x86_64` for x86_64 architecture.
|
Web server settings. If any of `host`/`port` is not set, web integration will be disabled. Group name must refer to architecture, e.g. it should be `web:x86_64` for x86_64 architecture.
|
||||||
|
|
||||||
* `address` - optional address in form `proto://host:port` (`port` can be omitted in case of default `proto` ports), will be used instead of `http://{host}:{port}` in case if set, string, optional. This option is required in case if `OAuth` provider is used.
|
* `address` - optional address in form `proto://host:port` (`port` can be omitted in case of default `proto` ports), will be used instead of `http://{host}:{port}` in case if set, string, optional. This option is required in case if `OAuth` provider is used.
|
||||||
|
* `debug` - enable debug toolbar, boolean, optional, default `no`.
|
||||||
|
* `debug_check_host` - check hosts to access debug toolbar, boolean, optional, default `no`.
|
||||||
|
* `debug_allowed_hosts` - allowed hosts to get access to debug toolbar, space separated list of string, optional.
|
||||||
* `host` - host to bind, string, optional.
|
* `host` - host to bind, string, optional.
|
||||||
|
* `index_url` - full url of the repository index page, string, optional.
|
||||||
* `password` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
* `password` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||||
* `port` - port to bind, int, optional.
|
* `port` - port to bind, int, optional.
|
||||||
* `static_path` - path to directory with static files, string, required.
|
* `static_path` - path to directory with static files, string, required.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Evgeniy Alekseev
|
# Maintainer: Evgeniy Alekseev
|
||||||
|
|
||||||
pkgname='ahriman'
|
pkgname='ahriman'
|
||||||
pkgver=1.3.0
|
pkgver=1.4.1
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH Linux ReposItory MANager"
|
pkgdesc="ArcH Linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
@ -15,6 +15,7 @@ optdepends=('breezy: -bzr packages support'
|
|||||||
'mercurial: -hg packages support'
|
'mercurial: -hg packages support'
|
||||||
'python-aioauth-client: web server with OAuth2 authorization'
|
'python-aioauth-client: web server with OAuth2 authorization'
|
||||||
'python-aiohttp: web server'
|
'python-aiohttp: web server'
|
||||||
|
'python-aiohttp-debugtoolbar: web server with enabled debug panel'
|
||||||
'python-aiohttp-jinja2: web server'
|
'python-aiohttp-jinja2: web server'
|
||||||
'python-aiohttp-security: web server with authorization'
|
'python-aiohttp-security: web server with authorization'
|
||||||
'python-aiohttp-session: web server with authorization'
|
'python-aiohttp-session: web server with authorization'
|
||||||
|
@ -10,10 +10,10 @@ root = /
|
|||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
target = disabled
|
target = disabled
|
||||||
allow_read_only = yes
|
|
||||||
max_age = 604800
|
max_age = 604800
|
||||||
oauth_provider = GoogleClient
|
oauth_provider = GoogleClient
|
||||||
oauth_scopes = https://www.googleapis.com/auth/userinfo.email
|
oauth_scopes = https://www.googleapis.com/auth/userinfo.email
|
||||||
|
safe_build_status = yes
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
archbuild_flags =
|
archbuild_flags =
|
||||||
@ -51,6 +51,9 @@ command = rsync --archive --compress --partial --delete
|
|||||||
chunk_size = 8388608
|
chunk_size = 8388608
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
|
debug = no
|
||||||
|
debug_check_host = no
|
||||||
|
debug_allowed_hosts =
|
||||||
host = 127.0.0.1
|
host = 127.0.0.1
|
||||||
static_path = /usr/share/ahriman/static
|
static_path = /usr/share/ahriman/static
|
||||||
templates = /usr/share/ahriman
|
templates = /usr/share/ahriman
|
@ -100,6 +100,12 @@
|
|||||||
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
|
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{% if index_url is not none %}
|
||||||
|
<ul class="nav">
|
||||||
|
<li><a class="nav-link" href="{{ index_url }}" title="repo index">repo index</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if auth.enabled %}
|
{% if auth.enabled %}
|
||||||
{% if auth.username is none %}
|
{% if auth.username is none %}
|
||||||
{{ auth.control|safe }}
|
{{ auth.control|safe }}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-success" data-bs-dismiss="modal" onclick="requestPackages()">Request</button>
|
||||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="addPackages()">Add</button>
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="addPackages()">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,8 @@
|
|||||||
success: function (resp) {
|
success: function (resp) {
|
||||||
const $options = resp.map(function (pkg) {
|
const $options = resp.map(function (pkg) {
|
||||||
const $option = document.createElement("option");
|
const $option = document.createElement("option");
|
||||||
$option.value = `${pkg.package} (${pkg.description})`;
|
$option.value = pkg.package;
|
||||||
|
$option.innerText = `${pkg.package} (${pkg.description})`;
|
||||||
return $option;
|
return $option;
|
||||||
});
|
});
|
||||||
$knownPackages.empty().append($options);
|
$knownPackages.empty().append($options);
|
||||||
@ -79,6 +80,11 @@
|
|||||||
doPackageAction("/service-api/v1/add", $packages);
|
doPackageAction("/service-api/v1/add", $packages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestPackages() {
|
||||||
|
const $packages = [$package.val()]
|
||||||
|
doPackageAction("/service-api/v1/request", $packages);
|
||||||
|
}
|
||||||
|
|
||||||
function removePackages() { doPackageAction("/service-api/v1/remove", getSelection()); }
|
function removePackages() { doPackageAction("/service-api/v1/remove", getSelection()); }
|
||||||
|
|
||||||
function updatePackages() { doPackageAction("/service-api/v1/add", getSelection()); }
|
function updatePackages() { doPackageAction("/service-api/v1/add", getSelection()); }
|
||||||
|
4
setup.py
4
setup.py
@ -81,6 +81,9 @@ setup(
|
|||||||
"package/share/ahriman/utils/bootstrap-scripts.jinja2",
|
"package/share/ahriman/utils/bootstrap-scripts.jinja2",
|
||||||
"package/share/ahriman/utils/style.jinja2",
|
"package/share/ahriman/utils/style.jinja2",
|
||||||
]),
|
]),
|
||||||
|
("share/man/man1", [
|
||||||
|
"docs/ahriman.1",
|
||||||
|
])
|
||||||
],
|
],
|
||||||
|
|
||||||
extras_require={
|
extras_require={
|
||||||
@ -107,6 +110,7 @@ setup(
|
|||||||
"aiohttp",
|
"aiohttp",
|
||||||
"aiohttp_jinja2",
|
"aiohttp_jinja2",
|
||||||
"aioauth-client",
|
"aioauth-client",
|
||||||
|
"aiohttp_debugtoolbar",
|
||||||
"aiohttp_session",
|
"aiohttp_session",
|
||||||
"aiohttp_security",
|
"aiohttp_security",
|
||||||
"cryptography",
|
"cryptography",
|
||||||
|
@ -26,6 +26,7 @@ from pathlib import Path
|
|||||||
from ahriman import version
|
from ahriman import version
|
||||||
from ahriman.application import handlers
|
from ahriman.application import handlers
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
|
from ahriman.models.package_source import PackageSource
|
||||||
from ahriman.models.sign_settings import SignSettings
|
from ahriman.models.sign_settings import SignSettings
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
|
|
||||||
@ -91,8 +92,10 @@ def _set_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("package", help="package base/name or archive path", nargs="+")
|
parser.add_argument("package", help="package base/name or archive path", nargs="+")
|
||||||
parser.add_argument("--now", help="run update function after", action="store_true")
|
parser.add_argument("--now", help="run update function after", action="store_true")
|
||||||
|
parser.add_argument("--source", help="package source", choices=PackageSource, type=PackageSource,
|
||||||
|
default=PackageSource.Auto)
|
||||||
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
|
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.Add, architecture=[])
|
parser.set_defaults(handler=handlers.Add)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -107,7 +110,7 @@ def _set_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("package", help="filter check by package base", nargs="*")
|
parser.add_argument("package", help="filter check by package base", nargs="*")
|
||||||
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
|
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.Update, architecture=[], no_aur=False, no_manual=True, dry_run=True)
|
parser.set_defaults(handler=handlers.Update, no_aur=False, no_manual=True, dry_run=True)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -124,7 +127,7 @@ def _set_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--no-chroot", help="do not clear build chroot", action="store_true")
|
parser.add_argument("--no-chroot", help="do not clear build chroot", action="store_true")
|
||||||
parser.add_argument("--no-manual", help="do not clear directory with manually added packages", action="store_true")
|
parser.add_argument("--no-manual", help="do not clear directory with manually added packages", action="store_true")
|
||||||
parser.add_argument("--no-packages", help="do not clear directory with built packages", action="store_true")
|
parser.add_argument("--no-packages", help="do not clear directory with built packages", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.Clean, architecture=[], no_log=True, unsafe=True)
|
parser.set_defaults(handler=handlers.Clean, no_log=True, unsafe=True)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +181,7 @@ def _set_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser = root.add_parser("rebuild", help="rebuild repository", description="rebuild whole repository",
|
parser = root.add_parser("rebuild", help="rebuild repository", description="rebuild whole repository",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append")
|
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append")
|
||||||
parser.set_defaults(handler=handlers.Rebuild, architecture=[])
|
parser.set_defaults(handler=handlers.Rebuild)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -191,7 +194,7 @@ def _set_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser = root.add_parser("remove", help="remove package", description="remove package",
|
parser = root.add_parser("remove", help="remove package", description="remove package",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("package", help="package name or base", nargs="+")
|
parser.add_argument("package", help="package name or base", nargs="+")
|
||||||
parser.set_defaults(handler=handlers.Remove, architecture=[])
|
parser.set_defaults(handler=handlers.Remove)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -205,7 +208,7 @@ def _set_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentParser
|
|||||||
description="remove packages which are missing in AUR",
|
description="remove packages which are missing in AUR",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true")
|
parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.RemoveUnknown, architecture=[])
|
parser.set_defaults(handler=handlers.RemoveUnknown)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -218,7 +221,7 @@ def _set_report_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser = root.add_parser("report", help="generate report", description="generate report",
|
parser = root.add_parser("report", help="generate report", description="generate report",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("target", help="target to generate report", nargs="*")
|
parser.add_argument("target", help="target to generate report", nargs="*")
|
||||||
parser.set_defaults(handler=handlers.Report, architecture=[])
|
parser.set_defaults(handler=handlers.Report)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -266,7 +269,7 @@ def _set_sign_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser = root.add_parser("sign", help="sign packages", description="(re-)sign packages and repository database",
|
parser = root.add_parser("sign", help="sign packages", description="(re-)sign packages and repository database",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("package", help="sign only specified packages", nargs="*")
|
parser.add_argument("package", help="sign only specified packages", nargs="*")
|
||||||
parser.set_defaults(handler=handlers.Sign, architecture=[])
|
parser.set_defaults(handler=handlers.Sign)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -313,7 +316,7 @@ def _set_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser = root.add_parser("sync", help="sync repository", description="sync packages to remote server",
|
parser = root.add_parser("sync", help="sync repository", description="sync packages to remote server",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("target", help="target to sync", nargs="*")
|
parser.add_argument("target", help="target to sync", nargs="*")
|
||||||
parser.set_defaults(handler=handlers.Sync, architecture=[])
|
parser.set_defaults(handler=handlers.Sync)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -330,7 +333,7 @@ def _set_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true")
|
parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true")
|
||||||
parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
|
parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
|
||||||
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
|
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.Update, architecture=[])
|
parser.set_defaults(handler=handlers.Update)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -357,6 +360,7 @@ def _set_user_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--no-reload", help="do not reload authentication module", action="store_true")
|
parser.add_argument("--no-reload", help="do not reload authentication module", action="store_true")
|
||||||
parser.add_argument("-p", "--password", help="user password")
|
parser.add_argument("-p", "--password", help="user password")
|
||||||
parser.add_argument("-r", "--remove", help="remove user from configuration", action="store_true")
|
parser.add_argument("-r", "--remove", help="remove user from configuration", action="store_true")
|
||||||
|
parser.add_argument("--secure", help="set file permissions to user-only", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.User, architecture=[""], lock=None, no_log=True, no_report=True, unsafe=True)
|
parser.set_defaults(handler=handlers.User, architecture=[""], lock=None, no_log=True, no_report=True, unsafe=True)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ from ahriman.core.repository.repository import Repository
|
|||||||
from ahriman.core.tree import Tree
|
from ahriman.core.tree import Tree
|
||||||
from ahriman.core.util import package_like
|
from ahriman.core.util import package_like
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.package_source import PackageSource
|
||||||
|
|
||||||
|
|
||||||
class Application:
|
class Application:
|
||||||
@ -96,10 +97,11 @@ class Application:
|
|||||||
|
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
def add(self, names: Iterable[str], without_dependencies: bool) -> None:
|
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:
|
||||||
"""
|
"""
|
||||||
add packages for the next build
|
add packages for the next build
|
||||||
:param names: list of package bases to add
|
:param names: list of package bases to add
|
||||||
|
:param source: package source to add
|
||||||
:param without_dependencies: if set, dependency check will be disabled
|
:param without_dependencies: if set, dependency check will be disabled
|
||||||
"""
|
"""
|
||||||
known_packages = self._known_packages()
|
known_packages = self._known_packages()
|
||||||
@ -122,14 +124,14 @@ class Application:
|
|||||||
if without_dependencies:
|
if without_dependencies:
|
||||||
return
|
return
|
||||||
dependencies = Package.dependencies(path)
|
dependencies = Package.dependencies(path)
|
||||||
self.add(dependencies.difference(known_packages), without_dependencies)
|
self.add(dependencies.difference(known_packages), PackageSource.AUR, without_dependencies)
|
||||||
|
|
||||||
def process_single(src: str) -> None:
|
def process_single(src: str) -> None:
|
||||||
maybe_path = Path(src)
|
resolved_source = source.resolve(src)
|
||||||
if maybe_path.is_dir():
|
if resolved_source == PackageSource.Directory:
|
||||||
add_directory(maybe_path)
|
add_directory(Path(src))
|
||||||
elif maybe_path.is_file():
|
elif resolved_source == PackageSource.Archive:
|
||||||
add_archive(maybe_path)
|
add_archive(Path(src))
|
||||||
else:
|
else:
|
||||||
path = add_manual(src)
|
path = add_manual(src)
|
||||||
process_dependencies(path)
|
process_dependencies(path)
|
||||||
|
@ -42,7 +42,7 @@ class Add(Handler):
|
|||||||
:param no_report: force disable reporting
|
:param no_report: force disable reporting
|
||||||
"""
|
"""
|
||||||
application = Application(architecture, configuration, no_report)
|
application = Application(architecture, configuration, no_report)
|
||||||
application.add(args.package, args.without_dependencies)
|
application.add(args.package, args.source, args.without_dependencies)
|
||||||
if not args.now:
|
if not args.now:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ class Dump(Handler):
|
|||||||
dump configuration handler
|
dump configuration handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||||
|
|
||||||
_print = print
|
_print = print
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -34,9 +34,11 @@ from ahriman.models.repository_paths import RepositoryPaths
|
|||||||
class Handler:
|
class Handler:
|
||||||
"""
|
"""
|
||||||
base handler class for command callbacks
|
base handler class for command callbacks
|
||||||
|
:cvar ALLOW_AUTO_ARCHITECTURE_RUN: allow to define architecture from existing repositories
|
||||||
:cvar ALLOW_MULTI_ARCHITECTURE_RUN: allow to run with multiple architectures
|
:cvar ALLOW_MULTI_ARCHITECTURE_RUN: allow to run with multiple architectures
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = True
|
||||||
ALLOW_MULTI_ARCHITECTURE_RUN = True
|
ALLOW_MULTI_ARCHITECTURE_RUN = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -85,9 +87,11 @@ class Handler:
|
|||||||
:param args: command line args
|
:param args: command line args
|
||||||
:return: list of architectures for which tree is created
|
:return: list of architectures for which tree is created
|
||||||
"""
|
"""
|
||||||
if args.architecture is None:
|
if not cls.ALLOW_AUTO_ARCHITECTURE_RUN and args.architecture is None:
|
||||||
|
# for some parsers (e.g. config) we need to run with specific architecture
|
||||||
|
# for those cases architecture must be set explicitly
|
||||||
raise MissingArchitecture(args.command)
|
raise MissingArchitecture(args.command)
|
||||||
if args.architecture:
|
if args.architecture: # architecture is specified explicitly
|
||||||
return set(args.architecture)
|
return set(args.architecture)
|
||||||
|
|
||||||
config = Configuration()
|
config = Configuration()
|
||||||
@ -96,7 +100,7 @@ class Handler:
|
|||||||
root = config.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
root = config.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
||||||
architectures = RepositoryPaths.known_architectures(root)
|
architectures = RepositoryPaths.known_architectures(root)
|
||||||
|
|
||||||
if not architectures:
|
if not architectures: # well we did not find anything
|
||||||
raise MissingArchitecture(args.command)
|
raise MissingArchitecture(args.command)
|
||||||
return architectures
|
return architectures
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ class Init(Handler):
|
|||||||
repository init handler
|
repository init handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
configuration: Configuration, no_report: bool) -> None:
|
configuration: Configuration, no_report: bool) -> None:
|
||||||
|
@ -31,6 +31,8 @@ class KeyImport(Handler):
|
|||||||
key import packages handler
|
key import packages handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
configuration: Configuration, no_report: bool) -> None:
|
configuration: Configuration, no_report: bool) -> None:
|
||||||
|
@ -31,6 +31,8 @@ class Search(Handler):
|
|||||||
packages search handler
|
packages search handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
configuration: Configuration, no_report: bool) -> None:
|
configuration: Configuration, no_report: bool) -> None:
|
||||||
|
@ -37,6 +37,8 @@ class Setup(Handler):
|
|||||||
:cvar SUDOERS_PATH: path to sudoers.d include configuration
|
:cvar SUDOERS_PATH: path to sudoers.d include configuration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||||
|
|
||||||
ARCHBUILD_COMMAND_PATH = Path("/usr/bin/archbuild")
|
ARCHBUILD_COMMAND_PATH = Path("/usr/bin/archbuild")
|
||||||
BIN_DIR_PATH = Path("/usr/local/bin")
|
BIN_DIR_PATH = Path("/usr/local/bin")
|
||||||
MIRRORLIST_PATH = Path("/etc/pacman.d/mirrorlist")
|
MIRRORLIST_PATH = Path("/etc/pacman.d/mirrorlist")
|
||||||
|
@ -33,6 +33,8 @@ class Status(Handler):
|
|||||||
package status handler
|
package status handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
configuration: Configuration, no_report: bool) -> None:
|
configuration: Configuration, no_report: bool) -> None:
|
||||||
|
@ -32,6 +32,8 @@ class StatusUpdate(Handler):
|
|||||||
status update handler
|
status update handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
configuration: Configuration, no_report: bool) -> None:
|
configuration: Configuration, no_report: bool) -> None:
|
||||||
|
@ -35,6 +35,8 @@ class User(Handler):
|
|||||||
user management handler
|
user management handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
configuration: Configuration, no_report: bool) -> None:
|
configuration: Configuration, no_report: bool) -> None:
|
||||||
@ -52,7 +54,7 @@ class User(Handler):
|
|||||||
User.clear_user(auth_configuration, user)
|
User.clear_user(auth_configuration, user)
|
||||||
if not args.remove:
|
if not args.remove:
|
||||||
User.create_configuration(auth_configuration, user, salt, args.as_service)
|
User.create_configuration(auth_configuration, user, salt, args.as_service)
|
||||||
User.write_configuration(auth_configuration)
|
User.write_configuration(auth_configuration, args.secure)
|
||||||
|
|
||||||
if not args.no_reload:
|
if not args.no_reload:
|
||||||
client = Application(architecture, configuration, no_report=False).repository.reporter
|
client = Application(architecture, configuration, no_report=False).repository.reporter
|
||||||
@ -127,13 +129,15 @@ class User(Handler):
|
|||||||
return MUser.generate_password(salt_length)
|
return MUser.generate_password(salt_length)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_configuration(configuration: Configuration) -> None:
|
def write_configuration(configuration: Configuration, secure: bool) -> None:
|
||||||
"""
|
"""
|
||||||
write configuration file
|
write configuration file
|
||||||
:param configuration: configuration instance
|
:param configuration: configuration instance
|
||||||
|
:param secure: if true then set file permissions to 0o600
|
||||||
"""
|
"""
|
||||||
if configuration.path is None:
|
if configuration.path is None:
|
||||||
return # should never happen actually
|
return # should never happen actually
|
||||||
with configuration.path.open("w") as ahriman_configuration:
|
with configuration.path.open("w") as ahriman_configuration:
|
||||||
configuration.write(ahriman_configuration)
|
configuration.write(ahriman_configuration)
|
||||||
configuration.path.chmod(0o600)
|
if secure:
|
||||||
|
configuration.path.chmod(0o600)
|
||||||
|
@ -31,6 +31,7 @@ class Web(Handler):
|
|||||||
web server handler
|
web server handler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes
|
ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -33,16 +33,11 @@ from ahriman.models.user_access import UserAccess
|
|||||||
class Auth:
|
class Auth:
|
||||||
"""
|
"""
|
||||||
helper to deal with user authorization
|
helper to deal with user authorization
|
||||||
:ivar allowed_paths: URI paths which can be accessed without authorization
|
|
||||||
:ivar allowed_paths_groups: URI paths prefixes which can be accessed without authorization
|
|
||||||
:ivar enabled: indicates if authorization is enabled
|
:ivar enabled: indicates if authorization is enabled
|
||||||
:cvar ALLOWED_PATHS: URI paths which can be accessed without authorization, predefined
|
:ivar max_age: session age in seconds. It will be used for both client side and server side checks
|
||||||
:cvar ALLOWED_PATHS_GROUPS: URI paths prefixes which can be accessed without authorization, predefined
|
:ivar safe_build_status: allow read only access to the index page
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ALLOWED_PATHS = {"/", "/index.html"}
|
|
||||||
ALLOWED_PATHS_GROUPS = {"/static", "/user-api"}
|
|
||||||
|
|
||||||
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
|
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
|
||||||
"""
|
"""
|
||||||
default constructor
|
default constructor
|
||||||
@ -51,11 +46,8 @@ class Auth:
|
|||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("http")
|
self.logger = logging.getLogger("http")
|
||||||
|
|
||||||
self.allow_read_only = configuration.getboolean("auth", "allow_read_only")
|
self.safe_build_status = configuration.getboolean("auth", "safe_build_status")
|
||||||
self.allowed_paths = set(configuration.getlist("auth", "allowed_paths", fallback=[]))
|
|
||||||
self.allowed_paths.update(self.ALLOWED_PATHS)
|
|
||||||
self.allowed_paths_groups = set(configuration.getlist("auth", "allowed_paths_groups", fallback=[]))
|
|
||||||
self.allowed_paths_groups.update(self.ALLOWED_PATHS_GROUPS)
|
|
||||||
self.enabled = provider.is_enabled
|
self.enabled = provider.is_enabled
|
||||||
self.max_age = configuration.getint("auth", "max_age", fallback=7 * 24 * 3600)
|
self.max_age = configuration.getint("auth", "max_age", fallback=7 * 24 * 3600)
|
||||||
|
|
||||||
@ -115,19 +107,6 @@ class Auth:
|
|||||||
del username, password
|
del username, password
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def is_safe_request(self, uri: Optional[str], required: UserAccess) -> bool:
|
|
||||||
"""
|
|
||||||
check if requested path are allowed without authorization
|
|
||||||
:param uri: request uri
|
|
||||||
:param required: required access level
|
|
||||||
:return: True in case if this URI can be requested without authorization and False otherwise
|
|
||||||
"""
|
|
||||||
if required == UserAccess.Read and self.allow_read_only:
|
|
||||||
return True # in case if read right requested and allowed in options
|
|
||||||
if not uri:
|
|
||||||
return False # request without context is not allowed
|
|
||||||
return uri in self.allowed_paths or any(uri.startswith(path) for path in self.allowed_paths_groups)
|
|
||||||
|
|
||||||
async def known_username(self, username: Optional[str]) -> bool: # pylint: disable=no-self-use
|
async def known_username(self, username: Optional[str]) -> bool: # pylint: disable=no-self-use
|
||||||
"""
|
"""
|
||||||
check if user is known
|
check if user is known
|
||||||
|
@ -28,6 +28,7 @@ from threading import Lock, Thread
|
|||||||
from typing import Callable, Dict, Iterable, Tuple
|
from typing import Callable, Dict, Iterable, Tuple
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.models.package_source import PackageSource
|
||||||
|
|
||||||
|
|
||||||
class Spawn(Thread):
|
class Spawn(Thread):
|
||||||
@ -79,7 +80,9 @@ class Spawn(Thread):
|
|||||||
:param packages: packages list to add
|
:param packages: packages list to add
|
||||||
:param now: build packages now
|
:param now: build packages now
|
||||||
"""
|
"""
|
||||||
kwargs = {"now": ""} if now else {}
|
kwargs = {"source": PackageSource.AUR.value} # avoid abusing by building non-aur packages
|
||||||
|
if now:
|
||||||
|
kwargs["now"] = ""
|
||||||
self.spawn_process("add", *packages, **kwargs)
|
self.spawn_process("add", *packages, **kwargs)
|
||||||
|
|
||||||
def packages_remove(self, packages: Iterable[str]) -> None:
|
def packages_remove(self, packages: Iterable[str]) -> None:
|
||||||
|
55
src/ahriman/models/package_source.py
Normal file
55
src/ahriman/models/package_source.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021 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 __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ahriman.core.util import package_like
|
||||||
|
|
||||||
|
|
||||||
|
class PackageSource(Enum):
|
||||||
|
"""
|
||||||
|
package source for addition enumeration
|
||||||
|
:cvar Auto: automatically determine type of the source
|
||||||
|
:cvar Archive: source is a package archive
|
||||||
|
:cvar Directory: source is a directory which contains packages
|
||||||
|
:cvar AUR: source is an AUR package for which it should search
|
||||||
|
"""
|
||||||
|
|
||||||
|
Auto = "auto"
|
||||||
|
Archive = "archive"
|
||||||
|
Directory = "directory"
|
||||||
|
AUR = "aur"
|
||||||
|
|
||||||
|
def resolve(self, source: str) -> PackageSource:
|
||||||
|
"""
|
||||||
|
resolve auto into the correct type
|
||||||
|
:param source: source of the package
|
||||||
|
:return: non-auto type of the package source
|
||||||
|
"""
|
||||||
|
if self != PackageSource.Auto:
|
||||||
|
return self
|
||||||
|
maybe_path = Path(source)
|
||||||
|
if maybe_path.is_dir():
|
||||||
|
return PackageSource.Directory
|
||||||
|
if maybe_path.is_file() and package_like(maybe_path):
|
||||||
|
return PackageSource.Archive
|
||||||
|
return PackageSource.AUR
|
@ -23,9 +23,11 @@ from enum import Enum
|
|||||||
class UserAccess(Enum):
|
class UserAccess(Enum):
|
||||||
"""
|
"""
|
||||||
web user access enumeration
|
web user access enumeration
|
||||||
:cvar Read: user can read status page
|
:cvar Safe: user can access the page without authorization, should not be user for user configuration
|
||||||
|
:cvar Read: user can read the page
|
||||||
:cvar Write: user can modify task and package list
|
:cvar Write: user can modify task and package list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Safe = "safe"
|
||||||
Read = "read"
|
Read = "read"
|
||||||
Write = "write"
|
Write = "write"
|
||||||
|
@ -17,4 +17,4 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "1.3.0"
|
__version__ = "1.4.1"
|
||||||
|
@ -19,10 +19,12 @@
|
|||||||
#
|
#
|
||||||
import aiohttp_security # type: ignore
|
import aiohttp_security # type: ignore
|
||||||
import base64
|
import base64
|
||||||
|
import types
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp.web import middleware, Request
|
from aiohttp.web import middleware, Request
|
||||||
from aiohttp.web_response import StreamResponse
|
from aiohttp.web_response import StreamResponse
|
||||||
|
from aiohttp.web_urldispatcher import StaticResource
|
||||||
from aiohttp_session import setup as setup_session # type: ignore
|
from aiohttp_session import setup as setup_session # type: ignore
|
||||||
from aiohttp_session.cookie_storage import EncryptedCookieStorage # type: ignore
|
from aiohttp_session.cookie_storage import EncryptedCookieStorage # type: ignore
|
||||||
from cryptography import fernet
|
from cryptography import fernet
|
||||||
@ -72,20 +74,22 @@ class AuthorizationPolicy(aiohttp_security.AbstractAuthorizationPolicy): # type
|
|||||||
return await self.validator.verify_access(user.username, permission, context)
|
return await self.validator.verify_access(user.username, permission, context)
|
||||||
|
|
||||||
|
|
||||||
def auth_handler(validator: Auth) -> MiddlewareType:
|
def auth_handler() -> MiddlewareType:
|
||||||
"""
|
"""
|
||||||
authorization and authentication middleware
|
authorization and authentication middleware
|
||||||
:param validator: authorization module instance
|
|
||||||
:return: built middleware
|
:return: built middleware
|
||||||
"""
|
"""
|
||||||
@middleware
|
@middleware
|
||||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
||||||
if request.method in ("GET", "HEAD", "OPTIONS"):
|
permission_method = getattr(handler, "get_permission", None)
|
||||||
permission = UserAccess.Read
|
if permission_method is not None:
|
||||||
|
permission = await permission_method(request)
|
||||||
|
elif isinstance(handler, types.MethodType): # additional wrapper for static resources
|
||||||
|
handler_instance = getattr(handler, "__self__", None)
|
||||||
|
permission = UserAccess.Safe if isinstance(handler_instance, StaticResource) else UserAccess.Write
|
||||||
else:
|
else:
|
||||||
permission = UserAccess.Write
|
permission = UserAccess.Write
|
||||||
|
if permission != UserAccess.Safe:
|
||||||
if not await validator.is_safe_request(request.path, permission):
|
|
||||||
await aiohttp_security.check_permission(request, permission, request.path)
|
await aiohttp_security.check_permission(request, permission, request.path)
|
||||||
|
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
@ -109,6 +113,6 @@ def setup_auth(application: web.Application, validator: Auth) -> web.Application
|
|||||||
identity_policy = aiohttp_security.SessionIdentityPolicy()
|
identity_policy = aiohttp_security.SessionIdentityPolicy()
|
||||||
|
|
||||||
aiohttp_security.setup(application, identity_policy, authorization_policy)
|
aiohttp_security.setup(application, identity_policy, authorization_policy)
|
||||||
application.middlewares.append(auth_handler(validator))
|
application.middlewares.append(auth_handler())
|
||||||
|
|
||||||
return application
|
return application
|
||||||
|
@ -24,6 +24,7 @@ from ahriman.web.views.index import IndexView
|
|||||||
from ahriman.web.views.service.add import AddView
|
from ahriman.web.views.service.add import AddView
|
||||||
from ahriman.web.views.service.reload_auth import ReloadAuthView
|
from ahriman.web.views.service.reload_auth import ReloadAuthView
|
||||||
from ahriman.web.views.service.remove import RemoveView
|
from ahriman.web.views.service.remove import RemoveView
|
||||||
|
from ahriman.web.views.service.request import RequestView
|
||||||
from ahriman.web.views.service.search import SearchView
|
from ahriman.web.views.service.search import SearchView
|
||||||
from ahriman.web.views.status.ahriman import AhrimanView
|
from ahriman.web.views.status.ahriman import AhrimanView
|
||||||
from ahriman.web.views.status.package import PackageView
|
from ahriman.web.views.status.package import PackageView
|
||||||
@ -48,6 +49,8 @@ def setup_routes(application: Application, static_path: Path) -> None:
|
|||||||
|
|
||||||
POST /service-api/v1/remove remove existing package from repository
|
POST /service-api/v1/remove remove existing package from repository
|
||||||
|
|
||||||
|
POST /service-api/v1/request request to add new packages to repository
|
||||||
|
|
||||||
GET /service-api/v1/search search for substring in AUR
|
GET /service-api/v1/search search for substring in AUR
|
||||||
|
|
||||||
POST /service-api/v1/update update packages in repository, actually it is just alias for add
|
POST /service-api/v1/update update packages in repository, actually it is just alias for add
|
||||||
@ -82,6 +85,8 @@ def setup_routes(application: Application, static_path: Path) -> None:
|
|||||||
|
|
||||||
application.router.add_post("/service-api/v1/remove", RemoveView)
|
application.router.add_post("/service-api/v1/remove", RemoveView)
|
||||||
|
|
||||||
|
application.router.add_post("/service-api/v1/request", RequestView)
|
||||||
|
|
||||||
application.router.add_get("/service-api/v1/search", SearchView, allow_head=False)
|
application.router.add_get("/service-api/v1/search", SearchView, allow_head=False)
|
||||||
|
|
||||||
application.router.add_post("/service-api/v1/update", AddView)
|
application.router.add_post("/service-api/v1/update", AddView)
|
||||||
|
@ -17,13 +17,16 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import View
|
from __future__ import annotations
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
from aiohttp.web import Request, View
|
||||||
|
from typing import Any, Dict, List, Optional, Type
|
||||||
|
|
||||||
from ahriman.core.auth.auth import Auth
|
from ahriman.core.auth.auth import Auth
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.spawn import Spawn
|
from ahriman.core.spawn import Spawn
|
||||||
from ahriman.core.status.watcher import Watcher
|
from ahriman.core.status.watcher import Watcher
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
|
||||||
|
|
||||||
class BaseView(View):
|
class BaseView(View):
|
||||||
@ -63,6 +66,16 @@ class BaseView(View):
|
|||||||
validator: Auth = self.request.app["validator"]
|
validator: Auth = self.request.app["validator"]
|
||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_permission(cls: Type[BaseView], request: Request) -> UserAccess:
|
||||||
|
"""
|
||||||
|
retrieve user permission from the request
|
||||||
|
:param request: request object
|
||||||
|
:return: extracted permission
|
||||||
|
"""
|
||||||
|
permission: UserAccess = getattr(cls, f"{request.method.upper()}_PERMISSION", UserAccess.Write)
|
||||||
|
return permission
|
||||||
|
|
||||||
async def extract_data(self, list_keys: Optional[List[str]] = None) -> Dict[str, Any]:
|
async def extract_data(self, list_keys: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
extract json data from either json or form data
|
extract json data from either json or form data
|
||||||
|
@ -24,12 +24,15 @@ from typing import Any, Dict
|
|||||||
from ahriman import version
|
from ahriman import version
|
||||||
from ahriman.core.auth.helpers import authorized_userid
|
from ahriman.core.auth.helpers import authorized_userid
|
||||||
from ahriman.core.util import pretty_datetime
|
from ahriman.core.util import pretty_datetime
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class IndexView(BaseView):
|
class IndexView(BaseView):
|
||||||
"""
|
"""
|
||||||
root view
|
root view
|
||||||
|
:cvar GET_PERMISSION: get permissions of self
|
||||||
|
:cvar HEAD_PERMISSION: head permissions of self
|
||||||
|
|
||||||
It uses jinja2 templates for report generation, the following variables are allowed:
|
It uses jinja2 templates for report generation, the following variables are allowed:
|
||||||
|
|
||||||
@ -39,6 +42,7 @@ class IndexView(BaseView):
|
|||||||
* control - HTML to insert for login control, HTML string, required
|
* control - HTML to insert for login control, HTML string, required
|
||||||
* enabled - whether authorization is enabled by configuration or not, boolean, required
|
* enabled - whether authorization is enabled by configuration or not, boolean, required
|
||||||
* username - authenticated username if any, string, null means not authenticated
|
* username - authenticated username if any, string, null means not authenticated
|
||||||
|
index_url - url to the repository index, string, optional
|
||||||
packages - sorted list of packages properties, required
|
packages - sorted list of packages properties, required
|
||||||
* base, string
|
* base, string
|
||||||
* depends, sorted list of strings
|
* depends, sorted list of strings
|
||||||
@ -58,6 +62,8 @@ class IndexView(BaseView):
|
|||||||
version - ahriman version, string, required
|
version - ahriman version, string, required
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Safe
|
||||||
|
|
||||||
@aiohttp_jinja2.template("build-status.jinja2")
|
@aiohttp_jinja2.template("build-status.jinja2")
|
||||||
async def get(self) -> Dict[str, Any]:
|
async def get(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@ -87,8 +93,9 @@ class IndexView(BaseView):
|
|||||||
|
|
||||||
# auth block
|
# auth block
|
||||||
auth_username = await authorized_userid(self.request)
|
auth_username = await authorized_userid(self.request)
|
||||||
|
authenticated = not self.validator.enabled or self.validator.safe_build_status or auth_username is not None
|
||||||
auth = {
|
auth = {
|
||||||
"authenticated": not self.validator.enabled or self.validator.allow_read_only or auth_username is not None,
|
"authenticated": authenticated,
|
||||||
"control": self.validator.auth_control,
|
"control": self.validator.auth_control,
|
||||||
"enabled": self.validator.enabled,
|
"enabled": self.validator.enabled,
|
||||||
"username": auth_username,
|
"username": auth_username,
|
||||||
@ -97,6 +104,7 @@ class IndexView(BaseView):
|
|||||||
return {
|
return {
|
||||||
"architecture": self.service.architecture,
|
"architecture": self.service.architecture,
|
||||||
"auth": auth,
|
"auth": auth,
|
||||||
|
"index_url": self.configuration.get("web", "index_url", fallback=None),
|
||||||
"packages": packages,
|
"packages": packages,
|
||||||
"repository": self.service.repository.name,
|
"repository": self.service.repository.name,
|
||||||
"service": service,
|
"service": service,
|
||||||
|
@ -19,22 +19,25 @@
|
|||||||
#
|
#
|
||||||
from aiohttp.web import HTTPFound, Response, json_response
|
from aiohttp.web import HTTPFound, Response, json_response
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class AddView(BaseView):
|
class AddView(BaseView):
|
||||||
"""
|
"""
|
||||||
add package web view
|
add package web view
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
POST_PERMISSION = UserAccess.Write
|
||||||
|
|
||||||
async def post(self) -> Response:
|
async def post(self) -> Response:
|
||||||
"""
|
"""
|
||||||
add new package
|
add new package
|
||||||
|
|
||||||
JSON body must be supplied, the following model is used:
|
JSON body must be supplied, the following model is used:
|
||||||
{
|
{
|
||||||
"packages": "ahriman", # either list of packages or package name as in AUR
|
"packages": "ahriman" # either list of packages or package name as in AUR
|
||||||
"build_now": true # optional flag which runs build
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:return: redirect to main page on success
|
:return: redirect to main page on success
|
||||||
@ -42,11 +45,10 @@ class AddView(BaseView):
|
|||||||
data = await self.extract_data(["packages"])
|
data = await self.extract_data(["packages"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
now = data.get("build_now", True)
|
|
||||||
packages = data["packages"]
|
packages = data["packages"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json_response(data=str(e), status=400)
|
return json_response(data=str(e), status=400)
|
||||||
|
|
||||||
self.spawner.packages_add(packages, now)
|
self.spawner.packages_add(packages, now=True)
|
||||||
|
|
||||||
return HTTPFound("/")
|
raise HTTPFound("/")
|
||||||
|
@ -17,18 +17,21 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import Response
|
from aiohttp.web import HTTPNoContent, Response
|
||||||
from aiohttp.web_exceptions import HTTPNoContent
|
|
||||||
|
|
||||||
from ahriman.core.auth.auth import Auth
|
from ahriman.core.auth.auth import Auth
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class ReloadAuthView(BaseView):
|
class ReloadAuthView(BaseView):
|
||||||
"""
|
"""
|
||||||
reload authentication module web view
|
reload authentication module web view
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
POST_PERMISSION = UserAccess.Write
|
||||||
|
|
||||||
async def post(self) -> Response:
|
async def post(self) -> Response:
|
||||||
"""
|
"""
|
||||||
reload authentication module. No parameters supported here
|
reload authentication module. No parameters supported here
|
||||||
@ -45,4 +48,4 @@ class ReloadAuthView(BaseView):
|
|||||||
self.request.app.logger.warning("could not update authentication module validator", exc_info=True)
|
self.request.app.logger.warning("could not update authentication module validator", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return HTTPNoContent()
|
raise HTTPNoContent()
|
||||||
|
@ -19,14 +19,18 @@
|
|||||||
#
|
#
|
||||||
from aiohttp.web import HTTPFound, Response, json_response
|
from aiohttp.web import HTTPFound, Response, json_response
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class RemoveView(BaseView):
|
class RemoveView(BaseView):
|
||||||
"""
|
"""
|
||||||
remove package web view
|
remove package web view
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
POST_PERMISSION = UserAccess.Write
|
||||||
|
|
||||||
async def post(self) -> Response:
|
async def post(self) -> Response:
|
||||||
"""
|
"""
|
||||||
remove existing packages
|
remove existing packages
|
||||||
@ -47,4 +51,4 @@ class RemoveView(BaseView):
|
|||||||
|
|
||||||
self.spawner.packages_remove(packages)
|
self.spawner.packages_remove(packages)
|
||||||
|
|
||||||
return HTTPFound("/")
|
raise HTTPFound("/")
|
||||||
|
54
src/ahriman/web/views/service/request.py
Normal file
54
src/ahriman/web/views/service/request.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from aiohttp.web import HTTPFound, Response, json_response
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class RequestView(BaseView):
|
||||||
|
"""
|
||||||
|
request package web view. It is actually the same as AddView, but without now
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
|
"""
|
||||||
|
|
||||||
|
POST_PERMISSION = UserAccess.Read
|
||||||
|
|
||||||
|
async def post(self) -> Response:
|
||||||
|
"""
|
||||||
|
request to add new package
|
||||||
|
|
||||||
|
JSON body must be supplied, the following model is used:
|
||||||
|
{
|
||||||
|
"packages": "ahriman" # either list of packages or package name as in AUR
|
||||||
|
}
|
||||||
|
|
||||||
|
:return: redirect to main page on success
|
||||||
|
"""
|
||||||
|
data = await self.extract_data(["packages"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
packages = data["packages"]
|
||||||
|
except Exception as e:
|
||||||
|
return json_response(data=str(e), status=400)
|
||||||
|
|
||||||
|
self.spawner.packages_add(packages, now=False)
|
||||||
|
|
||||||
|
raise HTTPFound("/")
|
@ -22,14 +22,19 @@ import aur # type: ignore
|
|||||||
from aiohttp.web import Response, json_response
|
from aiohttp.web import Response, json_response
|
||||||
from typing import Callable, Iterator
|
from typing import Callable, Iterator
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class SearchView(BaseView):
|
class SearchView(BaseView):
|
||||||
"""
|
"""
|
||||||
AUR search web view
|
AUR search web view
|
||||||
|
:cvar GET_PERMISSION: get permissions of self
|
||||||
|
:cvar HEAD_PERMISSION: head permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||||
|
|
||||||
async def get(self) -> Response:
|
async def get(self) -> Response:
|
||||||
"""
|
"""
|
||||||
search packages in AUR
|
search packages in AUR
|
||||||
|
@ -20,14 +20,21 @@
|
|||||||
from aiohttp.web import HTTPNoContent, Response, json_response
|
from aiohttp.web import HTTPNoContent, Response, json_response
|
||||||
|
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class AhrimanView(BaseView):
|
class AhrimanView(BaseView):
|
||||||
"""
|
"""
|
||||||
service status web view
|
service status web view
|
||||||
|
:cvar GET_PERMISSION: get permissions of self
|
||||||
|
:cvar HEAD_PERMISSION: head permissions of self
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||||
|
POST_PERMISSION = UserAccess.Write
|
||||||
|
|
||||||
async def get(self) -> Response:
|
async def get(self) -> Response:
|
||||||
"""
|
"""
|
||||||
get current service status
|
get current service status
|
||||||
@ -55,4 +62,4 @@ class AhrimanView(BaseView):
|
|||||||
|
|
||||||
self.service.update_self(status)
|
self.service.update_self(status)
|
||||||
|
|
||||||
return HTTPNoContent()
|
raise HTTPNoContent()
|
||||||
|
@ -22,14 +22,22 @@ from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
|
|||||||
from ahriman.core.exceptions import UnknownPackage
|
from ahriman.core.exceptions import UnknownPackage
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class PackageView(BaseView):
|
class PackageView(BaseView):
|
||||||
"""
|
"""
|
||||||
package base specific web view
|
package base specific web view
|
||||||
|
:cvar DELETE_PERMISSION: delete permissions of self
|
||||||
|
:cvar GET_PERMISSION: get permissions of self
|
||||||
|
:cvar HEAD_PERMISSION: head permissions of self
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Write
|
||||||
|
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||||
|
|
||||||
async def get(self) -> Response:
|
async def get(self) -> Response:
|
||||||
"""
|
"""
|
||||||
get current package base status
|
get current package base status
|
||||||
@ -58,7 +66,7 @@ class PackageView(BaseView):
|
|||||||
base = self.request.match_info["package"]
|
base = self.request.match_info["package"]
|
||||||
self.service.remove(base)
|
self.service.remove(base)
|
||||||
|
|
||||||
return HTTPNoContent()
|
raise HTTPNoContent()
|
||||||
|
|
||||||
async def post(self) -> Response:
|
async def post(self) -> Response:
|
||||||
"""
|
"""
|
||||||
@ -87,4 +95,4 @@ class PackageView(BaseView):
|
|||||||
except UnknownPackage:
|
except UnknownPackage:
|
||||||
return json_response(data=f"Package {base} is unknown, but no package body set", status=400)
|
return json_response(data=f"Package {base} is unknown, but no package body set", status=400)
|
||||||
|
|
||||||
return HTTPNoContent()
|
raise HTTPNoContent()
|
||||||
|
@ -19,14 +19,21 @@
|
|||||||
#
|
#
|
||||||
from aiohttp.web import HTTPNoContent, Response, json_response
|
from aiohttp.web import HTTPNoContent, Response, json_response
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class PackagesView(BaseView):
|
class PackagesView(BaseView):
|
||||||
"""
|
"""
|
||||||
global watcher view
|
global watcher view
|
||||||
|
:cvar GET_PERMISSION: get permissions of self
|
||||||
|
:cvar HEAD_PERMISSION: head permissions of self
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||||
|
POST_PERMISSION = UserAccess.Write
|
||||||
|
|
||||||
async def get(self) -> Response:
|
async def get(self) -> Response:
|
||||||
"""
|
"""
|
||||||
get current packages status
|
get current packages status
|
||||||
@ -47,4 +54,4 @@ class PackagesView(BaseView):
|
|||||||
"""
|
"""
|
||||||
self.service.load()
|
self.service.load()
|
||||||
|
|
||||||
return HTTPNoContent()
|
raise HTTPNoContent()
|
||||||
|
@ -22,14 +22,19 @@ from aiohttp.web import Response, json_response
|
|||||||
from ahriman import version
|
from ahriman import version
|
||||||
from ahriman.models.counters import Counters
|
from ahriman.models.counters import Counters
|
||||||
from ahriman.models.internal_status import InternalStatus
|
from ahriman.models.internal_status import InternalStatus
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class StatusView(BaseView):
|
class StatusView(BaseView):
|
||||||
"""
|
"""
|
||||||
web service status web view
|
web service status web view
|
||||||
|
:cvar GET_PERMISSION: get permissions of self
|
||||||
|
:cvar HEAD_PERMISSION: head permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||||
|
|
||||||
async def get(self) -> Response:
|
async def get(self) -> Response:
|
||||||
"""
|
"""
|
||||||
get current service status
|
get current service status
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
from aiohttp.web import HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized, Response
|
from aiohttp.web import HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized, Response
|
||||||
|
|
||||||
from ahriman.core.auth.helpers import remember
|
from ahriman.core.auth.helpers import remember
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.models.user_identity import UserIdentity
|
from ahriman.models.user_identity import UserIdentity
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
@ -27,8 +28,12 @@ from ahriman.web.views.base import BaseView
|
|||||||
class LoginView(BaseView):
|
class LoginView(BaseView):
|
||||||
"""
|
"""
|
||||||
login endpoint view
|
login endpoint view
|
||||||
|
:cvar GET_PERMISSION: get permissions of self
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
GET_PERMISSION = POST_PERMISSION = UserAccess.Safe
|
||||||
|
|
||||||
async def get(self) -> Response:
|
async def get(self) -> Response:
|
||||||
"""
|
"""
|
||||||
OAuth2 response handler
|
OAuth2 response handler
|
||||||
@ -46,7 +51,7 @@ class LoginView(BaseView):
|
|||||||
raise HTTPMethodNotAllowed(self.request.method, ["POST"])
|
raise HTTPMethodNotAllowed(self.request.method, ["POST"])
|
||||||
|
|
||||||
if not code:
|
if not code:
|
||||||
return HTTPFound(oauth_provider.get_oauth_url())
|
raise HTTPFound(oauth_provider.get_oauth_url())
|
||||||
|
|
||||||
response = HTTPFound("/")
|
response = HTTPFound("/")
|
||||||
username = await oauth_provider.get_oauth_username(code)
|
username = await oauth_provider.get_oauth_username(code)
|
||||||
|
@ -20,14 +20,18 @@
|
|||||||
from aiohttp.web import HTTPFound, Response
|
from aiohttp.web import HTTPFound, Response
|
||||||
|
|
||||||
from ahriman.core.auth.helpers import check_authorized, forget
|
from ahriman.core.auth.helpers import check_authorized, forget
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(BaseView):
|
class LogoutView(BaseView):
|
||||||
"""
|
"""
|
||||||
logout endpoint view
|
logout endpoint view
|
||||||
|
:cvar POST_PERMISSION: post permissions of self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
POST_PERMISSION = UserAccess.Safe
|
||||||
|
|
||||||
async def post(self) -> Response:
|
async def post(self) -> Response:
|
||||||
"""
|
"""
|
||||||
logout user from the service. No parameters supported here
|
logout user from the service. No parameters supported here
|
||||||
|
@ -99,6 +99,14 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw
|
|||||||
application.logger.info("setup process spawner")
|
application.logger.info("setup process spawner")
|
||||||
application["spawn"] = spawner
|
application["spawn"] = spawner
|
||||||
|
|
||||||
|
application.logger.info("setup debug panel")
|
||||||
|
debug_enabled = configuration.getboolean("web", "debug", fallback=False)
|
||||||
|
if debug_enabled:
|
||||||
|
import aiohttp_debugtoolbar # type: ignore
|
||||||
|
aiohttp_debugtoolbar.setup(application,
|
||||||
|
hosts=configuration.getlist("web", "debug_allowed_hosts", fallback=[]),
|
||||||
|
check_host=configuration.getboolean("web", "debug_check_host", fallback=False))
|
||||||
|
|
||||||
application.logger.info("setup authorization")
|
application.logger.info("setup authorization")
|
||||||
validator = application["validator"] = Auth.load(configuration)
|
validator = application["validator"] = Auth.load(configuration)
|
||||||
if validator.enabled:
|
if validator.enabled:
|
||||||
|
@ -27,7 +27,7 @@ def args() -> argparse.Namespace:
|
|||||||
fixture for command line arguments
|
fixture for command line arguments
|
||||||
:return: command line arguments test instance
|
:return: command line arguments test instance
|
||||||
"""
|
"""
|
||||||
return argparse.Namespace(lock=None, force=False, unsafe=False, no_report=True)
|
return argparse.Namespace(architecture=None, lock=None, force=False, unsafe=False, no_report=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -71,7 +71,6 @@ def test_extract_architectures(args: argparse.Namespace, configuration: Configur
|
|||||||
"""
|
"""
|
||||||
must generate list of available architectures
|
must generate list of available architectures
|
||||||
"""
|
"""
|
||||||
args.architecture = []
|
|
||||||
args.configuration = configuration.path
|
args.configuration = configuration.path
|
||||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||||
|
|
||||||
@ -84,7 +83,6 @@ def test_extract_architectures_empty(args: argparse.Namespace, configuration: Co
|
|||||||
"""
|
"""
|
||||||
must raise exception if no available architectures found
|
must raise exception if no available architectures found
|
||||||
"""
|
"""
|
||||||
args.architecture = []
|
|
||||||
args.command = "config"
|
args.command = "config"
|
||||||
args.configuration = configuration.path
|
args.configuration = configuration.path
|
||||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures", return_value=set())
|
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures", return_value=set())
|
||||||
@ -93,12 +91,12 @@ def test_extract_architectures_empty(args: argparse.Namespace, configuration: Co
|
|||||||
Handler.extract_architectures(args)
|
Handler.extract_architectures(args)
|
||||||
|
|
||||||
|
|
||||||
def test_extract_architectures_exception(args: argparse.Namespace) -> None:
|
def test_extract_architectures_exception(args: argparse.Namespace, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must raise exception on missing architectures
|
must raise exception on missing architectures
|
||||||
"""
|
"""
|
||||||
args.command = "config"
|
args.command = "config"
|
||||||
args.architecture = None
|
mocker.patch.object(Handler, "ALLOW_AUTO_ARCHITECTURE_RUN", False)
|
||||||
with pytest.raises(MissingArchitecture):
|
with pytest.raises(MissingArchitecture):
|
||||||
Handler.extract_architectures(args)
|
Handler.extract_architectures(args)
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from pytest_mock import MockerFixture
|
|||||||
|
|
||||||
from ahriman.application.handlers import Add
|
from ahriman.application.handlers import Add
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.models.package_source import PackageSource
|
||||||
|
|
||||||
|
|
||||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||||
@ -14,6 +15,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
|||||||
"""
|
"""
|
||||||
args.package = []
|
args.package = []
|
||||||
args.now = False
|
args.now = False
|
||||||
|
args.source = PackageSource.Auto
|
||||||
args.without_dependencies = False
|
args.without_dependencies = False
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
@ -18,3 +18,10 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
|||||||
Dump.run(args, "x86_64", configuration, True)
|
Dump.run(args, "x86_64", configuration, True)
|
||||||
application_mock.assert_called_once()
|
application_mock.assert_called_once()
|
||||||
print_mock.assert_called()
|
print_mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Dump.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -16,3 +16,10 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
|||||||
Init.run(args, "x86_64", configuration, True)
|
Init.run(args, "x86_64", configuration, True)
|
||||||
create_tree_mock.assert_called_once()
|
create_tree_mock.assert_called_once()
|
||||||
init_mock.assert_called_once()
|
init_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Init.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -27,3 +27,10 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
|||||||
|
|
||||||
KeyImport.run(args, "x86_64", configuration, True)
|
KeyImport.run(args, "x86_64", configuration, True)
|
||||||
application_mock.assert_called_once()
|
application_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not KeyImport.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -53,3 +53,10 @@ def test_log_fn(args: argparse.Namespace, configuration: Configuration, aur_pack
|
|||||||
|
|
||||||
Search.run(args, "x86_64", configuration, True)
|
Search.run(args, "x86_64", configuration, True)
|
||||||
print_mock.assert_called() # we don't really care about call details tbh
|
print_mock.assert_called() # we don't really care about call details tbh
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Search.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -149,3 +149,10 @@ def test_create_executable(args: argparse.Namespace, mocker: MockerFixture) -> N
|
|||||||
Setup.create_executable(args.build_command, "x86_64")
|
Setup.create_executable(args.build_command, "x86_64")
|
||||||
symlink_text_mock.assert_called_once()
|
symlink_text_mock.assert_called_once()
|
||||||
unlink_text_mock.assert_called_once()
|
unlink_text_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Setup.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -81,3 +81,10 @@ def test_imply_with_report(args: argparse.Namespace, configuration: Configuratio
|
|||||||
|
|
||||||
Status.run(args, "x86_64", configuration, True)
|
Status.run(args, "x86_64", configuration, True)
|
||||||
load_mock.assert_called_once()
|
load_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Status.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -86,3 +86,10 @@ def test_imply_with_report(args: argparse.Namespace, configuration: Configuratio
|
|||||||
|
|
||||||
StatusUpdate.run(args, "x86_64", configuration, True)
|
StatusUpdate.run(args, "x86_64", configuration, True)
|
||||||
load_mock.assert_called_once()
|
load_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not StatusUpdate.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -22,6 +22,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
|||||||
args.access = UserAccess.Read
|
args.access = UserAccess.Read
|
||||||
args.as_service = False
|
args.as_service = False
|
||||||
args.no_reload = False
|
args.no_reload = False
|
||||||
|
args.secure = False
|
||||||
args.remove = False
|
args.remove = False
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@ -227,11 +228,23 @@ def test_write_configuration(configuration: Configuration, mocker: MockerFixture
|
|||||||
write_mock = mocker.patch("ahriman.core.configuration.Configuration.write")
|
write_mock = mocker.patch("ahriman.core.configuration.Configuration.write")
|
||||||
chmod_mock = mocker.patch("pathlib.Path.chmod")
|
chmod_mock = mocker.patch("pathlib.Path.chmod")
|
||||||
|
|
||||||
User.write_configuration(configuration)
|
User.write_configuration(configuration, secure=True)
|
||||||
write_mock.assert_called_once()
|
write_mock.assert_called_once()
|
||||||
chmod_mock.assert_called_once()
|
chmod_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_configuration_insecure(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must write configuration without setting file permissions
|
||||||
|
"""
|
||||||
|
mocker.patch("pathlib.Path.open")
|
||||||
|
mocker.patch("ahriman.core.configuration.Configuration.write")
|
||||||
|
chmod_mock = mocker.patch("pathlib.Path.chmod")
|
||||||
|
|
||||||
|
User.write_configuration(configuration, secure=False)
|
||||||
|
chmod_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_write_configuration_not_loaded(configuration: Configuration, mocker: MockerFixture) -> None:
|
def test_write_configuration_not_loaded(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must do nothing in case if configuration is not loaded
|
must do nothing in case if configuration is not loaded
|
||||||
@ -241,6 +254,13 @@ def test_write_configuration_not_loaded(configuration: Configuration, mocker: Mo
|
|||||||
write_mock = mocker.patch("ahriman.core.configuration.Configuration.write")
|
write_mock = mocker.patch("ahriman.core.configuration.Configuration.write")
|
||||||
chmod_mock = mocker.patch("pathlib.Path.chmod")
|
chmod_mock = mocker.patch("pathlib.Path.chmod")
|
||||||
|
|
||||||
User.write_configuration(configuration)
|
User.write_configuration(configuration, secure=True)
|
||||||
write_mock.assert_not_called()
|
write_mock.assert_not_called()
|
||||||
chmod_mock.assert_not_called()
|
chmod_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not User.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
@ -31,6 +31,13 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
|||||||
run_mock.assert_called_once()
|
run_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallow_auto_architecture_run() -> None:
|
||||||
|
"""
|
||||||
|
must not allow multi architecture run
|
||||||
|
"""
|
||||||
|
assert not Web.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||||
|
|
||||||
|
|
||||||
def test_disallow_multi_architecture_run() -> None:
|
def test_disallow_multi_architecture_run() -> None:
|
||||||
"""
|
"""
|
||||||
must not allow multi architecture run
|
must not allow multi architecture run
|
||||||
|
@ -41,43 +41,64 @@ def test_multiple_architectures(parser: argparse.ArgumentParser) -> None:
|
|||||||
must accept multiple architectures
|
must accept multiple architectures
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["-a", "x86_64", "-a", "i686", "config"])
|
args = parser.parse_args(["-a", "x86_64", "-a", "i686", "config"])
|
||||||
assert len(args.architecture) == 2
|
assert args.architecture == ["x86_64", "i686"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_add(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_add_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
add command must imply empty architectures list
|
add command must correctly parse architecture list
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["add", "ahriman"])
|
args = parser.parse_args(["add", "ahriman"])
|
||||||
assert args.architecture == []
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "add", "ahriman"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_check(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_check(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
check command must imply empty architecture list, no-aur, no-manual and dry-run
|
check command must imply no-aur, no-manual and dry-run
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["check"])
|
args = parser.parse_args(["check"])
|
||||||
assert args.architecture == []
|
|
||||||
assert not args.no_aur
|
assert not args.no_aur
|
||||||
assert args.no_manual
|
assert args.no_manual
|
||||||
assert args.dry_run
|
assert args.dry_run
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_check_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
check command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["check"])
|
||||||
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "check"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_clean(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_clean(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
clean command must imply empty architectures list, unsafe and no-log
|
clean command must imply unsafe and no-log
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["clean"])
|
args = parser.parse_args(["clean"])
|
||||||
assert args.architecture == []
|
|
||||||
assert args.no_log
|
assert args.no_log
|
||||||
assert args.unsafe
|
assert args.unsafe
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_clean_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
clean command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["clean"])
|
||||||
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "clean"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_config(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_config(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
config command must imply lock, no-log, no-report and unsafe
|
config command must imply lock, no-log, no-report and unsafe
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["config"])
|
args = parser.parse_args(["-a", "x86_64", "config"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
assert args.lock is None
|
assert args.lock is None
|
||||||
assert args.no_log
|
assert args.no_log
|
||||||
assert args.no_report
|
assert args.no_report
|
||||||
@ -88,7 +109,8 @@ def test_subparsers_init(parser: argparse.ArgumentParser) -> None:
|
|||||||
"""
|
"""
|
||||||
init command must imply no_report
|
init command must imply no_report
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["init"])
|
args = parser.parse_args(["-a", "x86_64", "init"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
assert args.no_report
|
assert args.no_report
|
||||||
|
|
||||||
|
|
||||||
@ -102,36 +124,42 @@ def test_subparsers_key_import(parser: argparse.ArgumentParser) -> None:
|
|||||||
assert args.no_report
|
assert args.no_report
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_rebuild(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_key_import_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
rebuild command must imply empty architectures list
|
check command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "key-import", "key"])
|
||||||
|
assert args.architecture == [""]
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_rebuild_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
rebuild command must correctly parse architecture list
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["rebuild"])
|
args = parser.parse_args(["rebuild"])
|
||||||
assert args.architecture == []
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "rebuild"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_remove(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_remove_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
remove command must imply empty architectures list
|
remove command must correctly parse architecture list
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["remove", "ahriman"])
|
args = parser.parse_args(["remove", "ahriman"])
|
||||||
assert args.architecture == []
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "remove", "ahriman"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_remove_unknown(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_report_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
remove-unknown command must imply empty architectures list
|
report command must correctly parse architecture list
|
||||||
"""
|
|
||||||
args = parser.parse_args(["remove-unknown"])
|
|
||||||
assert args.architecture == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_report(parser: argparse.ArgumentParser) -> None:
|
|
||||||
"""
|
|
||||||
report command must imply empty architectures list
|
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["report"])
|
args = parser.parse_args(["report"])
|
||||||
assert args.architecture == []
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "report"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_search(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_search(parser: argparse.ArgumentParser) -> None:
|
||||||
@ -146,12 +174,21 @@ def test_subparsers_search(parser: argparse.ArgumentParser) -> None:
|
|||||||
assert args.unsafe
|
assert args.unsafe
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_search_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
search command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "search", "ahriman"])
|
||||||
|
assert args.architecture == [""]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_setup(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_setup(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
setup command must imply lock, no-log, no-report and unsafe
|
setup command must imply lock, no-log, no-report and unsafe
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["-a", "x86_64", "setup", "--packager", "John Doe <john@doe.com>",
|
args = parser.parse_args(["-a", "x86_64", "setup", "--packager", "John Doe <john@doe.com>",
|
||||||
"--repository", "aur-clone"])
|
"--repository", "aur-clone"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
assert args.lock is None
|
assert args.lock is None
|
||||||
assert args.no_log
|
assert args.no_log
|
||||||
assert args.no_report
|
assert args.no_report
|
||||||
@ -180,12 +217,14 @@ def test_subparsers_setup_option_sign_target(parser: argparse.ArgumentParser) ->
|
|||||||
assert all(isinstance(target, SignSettings) for target in args.sign_target)
|
assert all(isinstance(target, SignSettings) for target in args.sign_target)
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_sign(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_sign_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
sign command must imply empty architectures list
|
sign command must correctly parse architecture list
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["sign"])
|
args = parser.parse_args(["sign"])
|
||||||
assert args.architecture == []
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "sign"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_status(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_status(parser: argparse.ArgumentParser) -> None:
|
||||||
@ -193,6 +232,7 @@ def test_subparsers_status(parser: argparse.ArgumentParser) -> None:
|
|||||||
status command must imply lock, no-log, no-report and unsafe
|
status command must imply lock, no-log, no-report and unsafe
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["-a", "x86_64", "status"])
|
args = parser.parse_args(["-a", "x86_64", "status"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
assert args.lock is None
|
assert args.lock is None
|
||||||
assert args.no_log
|
assert args.no_log
|
||||||
assert args.no_report
|
assert args.no_report
|
||||||
@ -204,6 +244,7 @@ def test_subparsers_status_update(parser: argparse.ArgumentParser) -> None:
|
|||||||
status-update command must imply lock, no-log, no-report and unsafe
|
status-update command must imply lock, no-log, no-report and unsafe
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["-a", "x86_64", "status-update"])
|
args = parser.parse_args(["-a", "x86_64", "status-update"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
assert args.lock is None
|
assert args.lock is None
|
||||||
assert args.no_log
|
assert args.no_log
|
||||||
assert args.no_report
|
assert args.no_report
|
||||||
@ -220,20 +261,24 @@ def test_subparsers_status_update_option_status(parser: argparse.ArgumentParser)
|
|||||||
assert isinstance(args.status, BuildStatusEnum)
|
assert isinstance(args.status, BuildStatusEnum)
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_sync(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_sync_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
sync command must imply empty architectures list
|
sync command must correctly parse architecture list
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["sync"])
|
args = parser.parse_args(["sync"])
|
||||||
assert args.architecture == []
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "sync"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_update(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_update_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
update command must imply empty architectures list
|
update command must correctly parse architecture list
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["update"])
|
args = parser.parse_args(["update"])
|
||||||
assert args.architecture == []
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "update"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_user(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_user(parser: argparse.ArgumentParser) -> None:
|
||||||
@ -248,6 +293,14 @@ def test_subparsers_user(parser: argparse.ArgumentParser) -> None:
|
|||||||
assert args.unsafe
|
assert args.unsafe
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_user_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
user command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "user", "username"])
|
||||||
|
assert args.architecture == [""]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_user_option_role(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_user_option_role(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
user command must convert role option to useraccess instance
|
user command must convert role option to useraccess instance
|
||||||
@ -263,6 +316,7 @@ def test_subparsers_web(parser: argparse.ArgumentParser) -> None:
|
|||||||
web command must imply lock, no_report and parser
|
web command must imply lock, no_report and parser
|
||||||
"""
|
"""
|
||||||
args = parser.parse_args(["-a", "x86_64", "web"])
|
args = parser.parse_args(["-a", "x86_64", "web"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
assert args.lock is None
|
assert args.lock is None
|
||||||
assert args.no_report
|
assert args.no_report
|
||||||
assert args.parser is not None and args.parser()
|
assert args.parser is not None and args.parser()
|
||||||
|
@ -6,6 +6,7 @@ from unittest import mock
|
|||||||
from ahriman.application.application import Application
|
from ahriman.application.application import Application
|
||||||
from ahriman.core.tree import Leaf, Tree
|
from ahriman.core.tree import Leaf, Tree
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.package_source import PackageSource
|
||||||
|
|
||||||
|
|
||||||
def test_finalize(application: Application, mocker: MockerFixture) -> None:
|
def test_finalize(application: Application, mocker: MockerFixture) -> None:
|
||||||
@ -108,12 +109,11 @@ def test_add_directory(application: Application, package_ahriman: Package, mocke
|
|||||||
must add packages from directory
|
must add packages from directory
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
|
||||||
iterdir_mock = mocker.patch("pathlib.Path.iterdir",
|
iterdir_mock = mocker.patch("pathlib.Path.iterdir",
|
||||||
return_value=[package.filepath for package in package_ahriman.packages.values()])
|
return_value=[package.filepath for package in package_ahriman.packages.values()])
|
||||||
move_mock = mocker.patch("shutil.move")
|
move_mock = mocker.patch("shutil.move")
|
||||||
|
|
||||||
application.add([package_ahriman.base], False)
|
application.add([package_ahriman.base], PackageSource.Directory, False)
|
||||||
iterdir_mock.assert_called_once()
|
iterdir_mock.assert_called_once()
|
||||||
move_mock.assert_called_once()
|
move_mock.assert_called_once()
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ def test_add_manual(application: Application, package_ahriman: Package, mocker:
|
|||||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||||
fetch_mock = mocker.patch("ahriman.core.build_tools.task.Task.fetch")
|
fetch_mock = mocker.patch("ahriman.core.build_tools.task.Task.fetch")
|
||||||
|
|
||||||
application.add([package_ahriman.base], True)
|
application.add([package_ahriman.base], PackageSource.AUR, True)
|
||||||
fetch_mock.assert_called_once()
|
fetch_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ def test_add_manual_with_dependencies(application: Application, package_ahriman:
|
|||||||
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
|
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
|
||||||
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies")
|
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies")
|
||||||
|
|
||||||
application.add([package_ahriman.base], False)
|
application.add([package_ahriman.base], PackageSource.AUR, False)
|
||||||
dependencies_mock.assert_called_once()
|
dependencies_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
@ -149,10 +149,9 @@ def test_add_package(application: Application, package_ahriman: Package, mocker:
|
|||||||
must add package from archive
|
must add package from archive
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
|
||||||
move_mock = mocker.patch("shutil.move")
|
move_mock = mocker.patch("shutil.move")
|
||||||
|
|
||||||
application.add([package_ahriman.base], False)
|
application.add([package_ahriman.base], PackageSource.Archive, False)
|
||||||
move_mock.assert_called_once()
|
move_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@ -109,40 +109,6 @@ async def test_check_credentials(auth: Auth, user: User) -> None:
|
|||||||
assert await auth.check_credentials(None, None)
|
assert await auth.check_credentials(None, None)
|
||||||
|
|
||||||
|
|
||||||
async def test_is_safe_request(auth: Auth) -> None:
|
|
||||||
"""
|
|
||||||
must validate safe request
|
|
||||||
"""
|
|
||||||
# login and logout are always safe
|
|
||||||
assert await auth.is_safe_request("/user-api/v1/login", UserAccess.Write)
|
|
||||||
assert await auth.is_safe_request("/user-api/v1/logout", UserAccess.Write)
|
|
||||||
|
|
||||||
auth.allowed_paths.add("/safe")
|
|
||||||
auth.allowed_paths_groups.add("/unsafe/safe")
|
|
||||||
|
|
||||||
assert await auth.is_safe_request("/safe", UserAccess.Write)
|
|
||||||
assert not await auth.is_safe_request("/unsafe", UserAccess.Write)
|
|
||||||
assert await auth.is_safe_request("/unsafe/safe", UserAccess.Write)
|
|
||||||
assert await auth.is_safe_request("/unsafe/safe/suffix", UserAccess.Write)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_is_safe_request_empty(auth: Auth) -> None:
|
|
||||||
"""
|
|
||||||
must not allow requests without path
|
|
||||||
"""
|
|
||||||
assert not await auth.is_safe_request(None, UserAccess.Read)
|
|
||||||
assert not await auth.is_safe_request("", UserAccess.Read)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_is_safe_request_read_only(auth: Auth) -> None:
|
|
||||||
"""
|
|
||||||
must allow read-only requests if it is set in settings
|
|
||||||
"""
|
|
||||||
assert await auth.is_safe_request("/", UserAccess.Read)
|
|
||||||
auth.allow_read_only = True
|
|
||||||
assert await auth.is_safe_request("/unsafe", UserAccess.Read)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_known_username(auth: Auth, user: User) -> None:
|
async def test_known_username(auth: Auth, user: User) -> None:
|
||||||
"""
|
"""
|
||||||
must allow any username
|
must allow any username
|
||||||
|
@ -42,7 +42,7 @@ def test_packages_add(spawner: Spawn, mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
||||||
spawner.packages_add(["ahriman", "linux"], now=False)
|
spawner.packages_add(["ahriman", "linux"], now=False)
|
||||||
spawn_mock.assert_called_with("add", "ahriman", "linux")
|
spawn_mock.assert_called_with("add", "ahriman", "linux", source="aur")
|
||||||
|
|
||||||
|
|
||||||
def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
|
def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||||
@ -51,7 +51,7 @@ def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
||||||
spawner.packages_add(["ahriman", "linux"], now=True)
|
spawner.packages_add(["ahriman", "linux"], now=True)
|
||||||
spawn_mock.assert_called_with("add", "ahriman", "linux", now="")
|
spawn_mock.assert_called_with("add", "ahriman", "linux", source="aur", now="")
|
||||||
|
|
||||||
|
|
||||||
def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None:
|
def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||||
|
47
tests/ahriman/models/test_package_source.py
Normal file
47
tests/ahriman/models/test_package_source.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.models.package_source import PackageSource
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_non_auto() -> None:
|
||||||
|
"""
|
||||||
|
must resolve non auto type to itself
|
||||||
|
"""
|
||||||
|
for source in filter(lambda src: src != PackageSource.Auto, PackageSource):
|
||||||
|
assert source.resolve("") == source
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_archive(mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must resolve auto type into the archive
|
||||||
|
"""
|
||||||
|
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||||
|
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||||
|
assert PackageSource.Auto.resolve("linux-5.14.2.arch1-2-x86_64.pkg.tar.zst") == PackageSource.Archive
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_directory(mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must resolve auto type into the directory
|
||||||
|
"""
|
||||||
|
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||||
|
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||||
|
assert PackageSource.Auto.resolve("path") == PackageSource.Directory
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_aur(mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must resolve auto type into the AUR package
|
||||||
|
"""
|
||||||
|
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||||
|
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||||
|
assert PackageSource.Auto.resolve("package") == PackageSource.AUR
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_aur_not_package_like(mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must resolve auto type into the AUR package if it is file, but does not look like a package archive
|
||||||
|
"""
|
||||||
|
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||||
|
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||||
|
assert PackageSource.Auto.resolve("package") == PackageSource.AUR
|
@ -64,3 +64,20 @@ def application_with_auth(configuration: Configuration, user: User, spawner: Spa
|
|||||||
application["validator"]._users[generated.username] = generated
|
application["validator"]._users[generated.username] = generated
|
||||||
|
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def application_with_debug(configuration: Configuration, user: User, spawner: Spawn,
|
||||||
|
mocker: MockerFixture) -> web.Application:
|
||||||
|
"""
|
||||||
|
application fixture with debug enabled
|
||||||
|
:param configuration: configuration fixture
|
||||||
|
:param user: user descriptor fixture
|
||||||
|
:param spawner: spawner fixture
|
||||||
|
:param mocker: mocker object
|
||||||
|
:return: application test instance
|
||||||
|
"""
|
||||||
|
configuration.set_option("web", "debug", "yes")
|
||||||
|
mocker.patch.object(ahriman.core.auth.helpers, "_has_aiohttp_security", False)
|
||||||
|
mocker.patch("pathlib.Path.mkdir")
|
||||||
|
return setup_service("x86_64", configuration, spawner)
|
||||||
|
@ -43,60 +43,74 @@ async def test_permits(authorization_policy: AuthorizationPolicy, user: User) ->
|
|||||||
assert not await authorization_policy.permits(user.username, user.access, "/endpoint")
|
assert not await authorization_policy.permits(user.username, user.access, "/endpoint")
|
||||||
|
|
||||||
|
|
||||||
async def test_auth_handler_api(auth: Auth, mocker: MockerFixture) -> None:
|
async def test_auth_handler_api(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must ask for status permission for api calls
|
must ask for status permission for api calls
|
||||||
"""
|
"""
|
||||||
aiohttp_request = pytest.helpers.request("", "/status-api", "GET")
|
aiohttp_request = pytest.helpers.request("", "/status-api", "GET")
|
||||||
request_handler = AsyncMock()
|
request_handler = AsyncMock()
|
||||||
mocker.patch("ahriman.core.auth.auth.Auth.is_safe_request", return_value=False)
|
request_handler.get_permission.return_value = UserAccess.Read
|
||||||
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
||||||
|
|
||||||
handler = auth_handler(auth)
|
handler = auth_handler()
|
||||||
await handler(aiohttp_request, request_handler)
|
await handler(aiohttp_request, request_handler)
|
||||||
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Read, aiohttp_request.path)
|
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Read, aiohttp_request.path)
|
||||||
|
|
||||||
|
|
||||||
async def test_auth_handler_api_post(auth: Auth, mocker: MockerFixture) -> None:
|
async def test_auth_handler_api_no_method(mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must ask for write permission if handler does not have get_permission method
|
||||||
|
"""
|
||||||
|
aiohttp_request = pytest.helpers.request("", "/status-api", "GET")
|
||||||
|
request_handler = AsyncMock()
|
||||||
|
request_handler.get_permission = None
|
||||||
|
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
||||||
|
|
||||||
|
handler = auth_handler()
|
||||||
|
await handler(aiohttp_request, request_handler)
|
||||||
|
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Write, aiohttp_request.path)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_auth_handler_api_post(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must ask for status permission for api calls with POST
|
must ask for status permission for api calls with POST
|
||||||
"""
|
"""
|
||||||
aiohttp_request = pytest.helpers.request("", "/status-api", "POST")
|
aiohttp_request = pytest.helpers.request("", "/status-api", "POST")
|
||||||
request_handler = AsyncMock()
|
request_handler = AsyncMock()
|
||||||
mocker.patch("ahriman.core.auth.auth.Auth.is_safe_request", return_value=False)
|
request_handler.get_permission.return_value = UserAccess.Write
|
||||||
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
||||||
|
|
||||||
handler = auth_handler(auth)
|
handler = auth_handler()
|
||||||
await handler(aiohttp_request, request_handler)
|
await handler(aiohttp_request, request_handler)
|
||||||
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Write, aiohttp_request.path)
|
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Write, aiohttp_request.path)
|
||||||
|
|
||||||
|
|
||||||
async def test_auth_handler_read(auth: Auth, mocker: MockerFixture) -> None:
|
async def test_auth_handler_read(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must ask for read permission for api calls with GET
|
must ask for read permission for api calls with GET
|
||||||
"""
|
"""
|
||||||
for method in ("GET", "HEAD", "OPTIONS"):
|
for method in ("GET", "HEAD", "OPTIONS"):
|
||||||
aiohttp_request = pytest.helpers.request("", "", method)
|
aiohttp_request = pytest.helpers.request("", "", method)
|
||||||
request_handler = AsyncMock()
|
request_handler = AsyncMock()
|
||||||
mocker.patch("ahriman.core.auth.auth.Auth.is_safe_request", return_value=False)
|
request_handler.get_permission.return_value = UserAccess.Read
|
||||||
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
||||||
|
|
||||||
handler = auth_handler(auth)
|
handler = auth_handler()
|
||||||
await handler(aiohttp_request, request_handler)
|
await handler(aiohttp_request, request_handler)
|
||||||
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Read, aiohttp_request.path)
|
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Read, aiohttp_request.path)
|
||||||
|
|
||||||
|
|
||||||
async def test_auth_handler_write(auth: Auth, mocker: MockerFixture) -> None:
|
async def test_auth_handler_write(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must ask for read permission for api calls with POST
|
must ask for read permission for api calls with POST
|
||||||
"""
|
"""
|
||||||
for method in ("CONNECT", "DELETE", "PATCH", "POST", "PUT", "TRACE"):
|
for method in ("CONNECT", "DELETE", "PATCH", "POST", "PUT", "TRACE"):
|
||||||
aiohttp_request = pytest.helpers.request("", "", method)
|
aiohttp_request = pytest.helpers.request("", "", method)
|
||||||
request_handler = AsyncMock()
|
request_handler = AsyncMock()
|
||||||
mocker.patch("ahriman.core.auth.auth.Auth.is_safe_request", return_value=False)
|
request_handler.get_permission.return_value = UserAccess.Write
|
||||||
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
check_permission_mock = mocker.patch("aiohttp_security.check_permission")
|
||||||
|
|
||||||
handler = auth_handler(auth)
|
handler = auth_handler()
|
||||||
await handler(aiohttp_request, request_handler)
|
await handler(aiohttp_request, request_handler)
|
||||||
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Write, aiohttp_request.path)
|
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Write, aiohttp_request.path)
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ def test_run(application: web.Application, mocker: MockerFixture) -> None:
|
|||||||
|
|
||||||
def test_run_with_auth(application_with_auth: web.Application, mocker: MockerFixture) -> None:
|
def test_run_with_auth(application_with_auth: web.Application, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must run application
|
must run application with enabled authorization
|
||||||
"""
|
"""
|
||||||
port = 8080
|
port = 8080
|
||||||
application_with_auth["configuration"].set_option("web", "port", str(port))
|
application_with_auth["configuration"].set_option("web", "port", str(port))
|
||||||
@ -54,3 +54,16 @@ def test_run_with_auth(application_with_auth: web.Application, mocker: MockerFix
|
|||||||
run_server(application_with_auth)
|
run_server(application_with_auth)
|
||||||
run_application_mock.assert_called_with(application_with_auth, host="127.0.0.1", port=port,
|
run_application_mock.assert_called_with(application_with_auth, host="127.0.0.1", port=port,
|
||||||
handle_signals=False, access_log=pytest.helpers.anyvar(int))
|
handle_signals=False, access_log=pytest.helpers.anyvar(int))
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_with_debug(application_with_debug: web.Application, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must run application with enabled debug panel
|
||||||
|
"""
|
||||||
|
port = 8080
|
||||||
|
application_with_debug["configuration"].set_option("web", "port", str(port))
|
||||||
|
run_application_mock = mocker.patch("aiohttp.web.run_app")
|
||||||
|
|
||||||
|
run_server(application_with_debug)
|
||||||
|
run_application_mock.assert_called_with(application_with_debug, host="127.0.0.1", port=port,
|
||||||
|
handle_signals=False, access_log=pytest.helpers.anyvar(int))
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.service.add import AddView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("POST",):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await AddView.get_permission(request) == UserAccess.Write
|
||||||
|
|
||||||
|
|
||||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
@ -10,18 +24,7 @@ async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
|||||||
response = await client.post("/service-api/v1/add", json={"packages": ["ahriman"]})
|
response = await client.post("/service-api/v1/add", json={"packages": ["ahriman"]})
|
||||||
|
|
||||||
assert response.ok
|
assert response.ok
|
||||||
add_mock.assert_called_with(["ahriman"], True)
|
add_mock.assert_called_with(["ahriman"], now=True)
|
||||||
|
|
||||||
|
|
||||||
async def test_post_now(client: TestClient, mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must call post and run build
|
|
||||||
"""
|
|
||||||
add_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_add")
|
|
||||||
response = await client.post("/service-api/v1/add", json={"packages": ["ahriman"], "build_now": False})
|
|
||||||
|
|
||||||
assert response.ok
|
|
||||||
add_mock.assert_called_with(["ahriman"], False)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_post_exception(client: TestClient, mocker: MockerFixture) -> None:
|
async def test_post_exception(client: TestClient, mocker: MockerFixture) -> None:
|
||||||
@ -43,4 +46,4 @@ async def test_post_update(client: TestClient, mocker: MockerFixture) -> None:
|
|||||||
response = await client.post("/service-api/v1/update", json={"packages": ["ahriman"]})
|
response = await client.post("/service-api/v1/update", json={"packages": ["ahriman"]})
|
||||||
|
|
||||||
assert response.ok
|
assert response.ok
|
||||||
add_mock.assert_called_with(["ahriman"], True)
|
add_mock.assert_called_with(["ahriman"], now=True)
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.service.reload_auth import ReloadAuthView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("POST",):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await ReloadAuthView.get_permission(request) == UserAccess.Write
|
||||||
|
|
||||||
|
|
||||||
async def test_post(client_with_auth: TestClient, mocker: MockerFixture) -> None:
|
async def test_post(client_with_auth: TestClient, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.service.remove import RemoveView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("POST",):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await RemoveView.get_permission(request) == UserAccess.Write
|
||||||
|
|
||||||
|
|
||||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiohttp.test_utils import TestClient
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.service.request import RequestView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("POST",):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await RequestView.get_permission(request) == UserAccess.Read
|
||||||
|
|
||||||
|
|
||||||
|
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must call post request correctly
|
||||||
|
"""
|
||||||
|
add_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_add")
|
||||||
|
response = await client.post("/service-api/v1/request", json={"packages": ["ahriman"]})
|
||||||
|
|
||||||
|
assert response.ok
|
||||||
|
add_mock.assert_called_with(["ahriman"], now=False)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_post_exception(client: TestClient, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must raise exception on missing packages payload
|
||||||
|
"""
|
||||||
|
add_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_add")
|
||||||
|
response = await client.post("/service-api/v1/request")
|
||||||
|
|
||||||
|
assert response.status == 400
|
||||||
|
add_mock.assert_not_called()
|
@ -1,8 +1,21 @@
|
|||||||
import aur
|
import aur
|
||||||
|
import pytest
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.service.search import SearchView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("GET", "HEAD"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await SearchView.get_permission(request) == UserAccess.Read
|
||||||
|
|
||||||
|
|
||||||
async def test_get(client: TestClient, aur_package_ahriman: aur.Package, mocker: MockerFixture) -> None:
|
async def test_get(client: TestClient, aur_package_ahriman: aur.Package, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1,7 +1,23 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.status.ahriman import AhrimanView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("GET", "HEAD"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await AhrimanView.get_permission(request) == UserAccess.Read
|
||||||
|
for method in ("POST",):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await AhrimanView.get_permission(request) == UserAccess.Write
|
||||||
|
|
||||||
|
|
||||||
async def test_get(client: TestClient) -> None:
|
async def test_get(client: TestClient) -> None:
|
||||||
|
@ -1,7 +1,23 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from pytest_aiohttp import TestClient
|
from pytest_aiohttp import TestClient
|
||||||
|
|
||||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.status.package import PackageView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("GET", "HEAD"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await PackageView.get_permission(request) == UserAccess.Read
|
||||||
|
for method in ("DELETE", "POST"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await PackageView.get_permission(request) == UserAccess.Write
|
||||||
|
|
||||||
|
|
||||||
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||||
|
@ -1,8 +1,24 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from pytest_aiohttp import TestClient
|
from pytest_aiohttp import TestClient
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.status.packages import PackagesView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("GET", "HEAD"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await PackagesView.get_permission(request) == UserAccess.Read
|
||||||
|
for method in ("POST",):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await PackagesView.get_permission(request) == UserAccess.Write
|
||||||
|
|
||||||
|
|
||||||
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||||
|
@ -1,9 +1,22 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from pytest_aiohttp import TestClient
|
from pytest_aiohttp import TestClient
|
||||||
|
|
||||||
import ahriman.version as version
|
import ahriman.version as version
|
||||||
|
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.status.status import StatusView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("GET", "HEAD"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await StatusView.get_permission(request) == UserAccess.Read
|
||||||
|
|
||||||
|
|
||||||
async def test_get(client: TestClient, package_ahriman: Package) -> None:
|
async def test_get(client: TestClient, package_ahriman: Package) -> None:
|
||||||
|
@ -33,6 +33,16 @@ def test_validator(base: BaseView) -> None:
|
|||||||
assert base.validator
|
assert base.validator
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission(base: BaseView) -> None:
|
||||||
|
"""
|
||||||
|
must search for permission attribute in class
|
||||||
|
"""
|
||||||
|
for method in ("DELETE", "GET", "HEAD", "POST"):
|
||||||
|
request = pytest.helpers.request(base.request.app, "", method)
|
||||||
|
setattr(BaseView, f"{method.upper()}_PERMISSION", "permission")
|
||||||
|
assert await base.get_permission(request) == "permission"
|
||||||
|
|
||||||
|
|
||||||
async def test_extract_data_json(base: BaseView) -> None:
|
async def test_extract_data_json(base: BaseView) -> None:
|
||||||
"""
|
"""
|
||||||
must parse and return json
|
must parse and return json
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from pytest_aiohttp import TestClient
|
from pytest_aiohttp import TestClient
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.index import IndexView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("GET", "HEAD"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await IndexView.get_permission(request) == UserAccess.Safe
|
||||||
|
|
||||||
|
|
||||||
async def test_get(client_with_auth: TestClient) -> None:
|
async def test_get(client_with_auth: TestClient) -> None:
|
||||||
"""
|
"""
|
||||||
@ -34,3 +48,11 @@ async def test_get_static(client: TestClient) -> None:
|
|||||||
"""
|
"""
|
||||||
response = await client.get("/static/favicon.ico")
|
response = await client.get("/static/favicon.ico")
|
||||||
assert response.ok
|
assert response.ok
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_static_with_auth(client_with_auth: TestClient) -> None:
|
||||||
|
"""
|
||||||
|
must return static files
|
||||||
|
"""
|
||||||
|
response = await client_with_auth.get("/static/favicon.ico")
|
||||||
|
assert response.ok
|
||||||
|
@ -1,9 +1,21 @@
|
|||||||
|
import pytest
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from ahriman.core.auth.oauth import OAuth
|
from ahriman.core.auth.oauth import OAuth
|
||||||
from ahriman.models.user import User
|
from ahriman.models.user import User
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.user.login import LoginView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("GET", "POST"):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await LoginView.get_permission(request) == UserAccess.Safe
|
||||||
|
|
||||||
|
|
||||||
async def test_get_default_validator(client_with_auth: TestClient) -> None:
|
async def test_get_default_validator(client_with_auth: TestClient) -> None:
|
||||||
|
@ -1,7 +1,21 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from aiohttp.web import HTTPUnauthorized
|
from aiohttp.web import HTTPUnauthorized
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.models.user_access import UserAccess
|
||||||
|
from ahriman.web.views.user.logout import LogoutView
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_permission() -> None:
|
||||||
|
"""
|
||||||
|
must return correct permission for the request
|
||||||
|
"""
|
||||||
|
for method in ("POST",):
|
||||||
|
request = pytest.helpers.request("", "", method)
|
||||||
|
assert await LogoutView.get_permission(request) == UserAccess.Safe
|
||||||
|
|
||||||
|
|
||||||
async def test_post(client_with_auth: TestClient, mocker: MockerFixture) -> None:
|
async def test_post(client_with_auth: TestClient, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -9,12 +9,12 @@ repositories = core extra community multilib
|
|||||||
root = /
|
root = /
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
allow_read_only = no
|
|
||||||
client_id = client_id
|
client_id = client_id
|
||||||
client_secret = client_secret
|
client_secret = client_secret
|
||||||
oauth_provider = GoogleClient
|
oauth_provider = GoogleClient
|
||||||
oauth_scopes = https://www.googleapis.com/auth/userinfo.email
|
oauth_scopes = https://www.googleapis.com/auth/userinfo.email
|
||||||
salt = salt
|
salt = salt
|
||||||
|
safe_build_status = no
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
archbuild_flags =
|
archbuild_flags =
|
||||||
@ -62,6 +62,9 @@ region = eu-central-1
|
|||||||
secret_key =
|
secret_key =
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
|
debug = no
|
||||||
|
debug_check_host = no
|
||||||
|
debug_allowed_hosts =
|
||||||
host = 127.0.0.1
|
host = 127.0.0.1
|
||||||
static_path = ../web/templates/static
|
static_path = ../web/templates/static
|
||||||
templates = ../web/templates
|
templates = ../web/templates
|
Reference in New Issue
Block a user