mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-27 14:22:10 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
42c13b5d4b | |||
04e5a263b7 | |||
caca1576c8 | |||
98f2f19d5b | |||
5c4d3eeffd | |||
84d4523e85 | |||
2c2eae2334 | |||
214d6d7fdd | |||
e9512e9a6a | |||
f984ea75d0 | |||
b44e5c3a3d | |||
1891d6c944 | |||
08d68258ca | |||
cd3d12a754 | |||
81d9526054 | |||
c7447f19f0 | |||
7de94a8daa | |||
aa1a59d0a3 | |||
8864855c14 | |||
b0b37e8169 | |||
7df4adfc9d | |||
2d5b73c6dc | |||
1c8aed4a97 | |||
2e059023f3 | |||
da5d97788a | |||
0e839fbbf2 | |||
262462d3c3 | |||
7aa91f9e2e | |||
01eda513cf | |||
0161617e36 | |||
4811dec759 | |||
09623c20d5 | |||
eba247b759 | |||
5073c80af1 | |||
766081d212 | |||
896cd0bd71 | |||
ce1bd2f2db | |||
6ba96d838d | |||
df9e03f495 | |||
89944eb2b6 | |||
336784519b | |||
f3341ec7cd | |||
d36e851a29 | |||
28bd5f2095 | |||
011b4e2e31 | |||
15609ba044 | |||
6d4f9981f7 | |||
bbb97d1cdd | |||
3bca780bdd | |||
78e6b48c24 | |||
137d62e2f8 | |||
8a6854c867 | |||
299732181c | |||
84c1b4d82d | |||
cdd66ee780 | |||
b2ed383de0 | |||
551ee670bf |
20
.github/ISSUE_TEMPLATE/02-security-report.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/02-security-report.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Security report
|
||||
about: Create a report related to security issues
|
||||
title: ''
|
||||
labels: security
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
A clear and concise description of what the issue is.
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
Steps to reproduce the behavior (commands, environment etc).
|
||||
|
||||
### Intended impact
|
||||
|
||||
Brief optional description of how this vulnerability can be used and which effects can be achieved.
|
2
.github/workflows/setup.sh
vendored
2
.github/workflows/setup.sh
vendored
@ -52,7 +52,7 @@ fi
|
||||
# the build itself does not really work in the container
|
||||
sudo -u ahriman -- ahriman package-add --now yay
|
||||
# check if package was actually installed
|
||||
#test -n "$(find "/var/lib/ahriman/repository/x86_64" -name "yay*pkg*")"
|
||||
test -n "$(find "/var/lib/ahriman/repository/x86_64" -name "yay*pkg*")"
|
||||
# run package check
|
||||
sudo -u ahriman -- ahriman repo-update
|
||||
# stop web service lol
|
||||
|
@ -1,6 +1,7 @@
|
||||
version: 2
|
||||
|
||||
formats: all
|
||||
formats:
|
||||
- pdf
|
||||
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
@ -10,6 +11,7 @@ build:
|
||||
sphinx:
|
||||
builder: html
|
||||
configuration: docs/conf.py
|
||||
fail_on_warning: true
|
||||
|
||||
python:
|
||||
install:
|
||||
@ -17,4 +19,6 @@ python:
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
- s3
|
||||
- web
|
||||
system_packages: true
|
||||
|
4
AUTHORS
4
AUTHORS
@ -1,2 +1,2 @@
|
||||
Current developers:
|
||||
Evgenii Alekseev aka arcanis <esalexeev (at) gmail (dot) com>
|
||||
Current maintainer:
|
||||
Evgenii Alekseev <esalexeev (at) gmail (dot) com>
|
||||
|
@ -26,7 +26,95 @@ In order to resolve all difficult cases the `autopep8` is used. You can perform
|
||||
Again, the most checks can be performed by `make check` command, though some additional guidelines must be applied:
|
||||
|
||||
* Every class, every function (including private and protected), every attribute must be documented. The project follows [Google style documentation](https://google.github.io/styleguide/pyguide.html). The only exception is local functions.
|
||||
* Correct way to document function, if section is empty, e.g. no notes or there are no args, it should be omitted:
|
||||
|
||||
```python
|
||||
def foo(argument: str, *, flag: bool = False) -> int:
|
||||
"""
|
||||
do foo
|
||||
|
||||
Note:
|
||||
Very important note about this function
|
||||
|
||||
Args:
|
||||
argument(str): an argument
|
||||
flag(bool, optional): a flag (Default value = False)
|
||||
|
||||
Returns:
|
||||
int: result
|
||||
|
||||
Raises:
|
||||
RuntimeException: a local function error occurs
|
||||
|
||||
Examples:
|
||||
Very informative example how to use this function, e.g.::
|
||||
|
||||
>>> foo("argument", flag=False)
|
||||
|
||||
Note that function documentation is in rST.
|
||||
"""
|
||||
```
|
||||
|
||||
`Returns` should be replaced with `Yields` for generators.
|
||||
|
||||
Class attributes should be documented in the following way:
|
||||
|
||||
```python
|
||||
class Clazz(BaseClazz):
|
||||
"""
|
||||
brand-new implementation of ``BaseClazz``
|
||||
|
||||
Attributes:
|
||||
CLAZZ_ATTRIBUTE(int): (class attribute) a brand-new class attribute
|
||||
instance_attribute(str): an instance attribute
|
||||
|
||||
Examples:
|
||||
Very informative class usage example, e.g.::
|
||||
|
||||
>>> from module import Clazz
|
||||
>>> clazz = Clazz()
|
||||
"""
|
||||
|
||||
CLAZZ_ATTRIBUTE = 42
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
default constructor
|
||||
"""
|
||||
self.instance_attribute = ""
|
||||
```
|
||||
|
||||
* Type annotations are the must, even for local functions.
|
||||
* Recommended order of function definitions in class:
|
||||
|
||||
```python
|
||||
class Clazz:
|
||||
|
||||
def __init__(self) -> None: ... # replace with `__post_init__` for dataclasses
|
||||
|
||||
@property
|
||||
def property(self) -> Any: ...
|
||||
|
||||
@classmethod
|
||||
def class_method(cls: Type[Clazz]) -> Clazz: ...
|
||||
|
||||
@staticmethod
|
||||
def static_method() -> Any: ...
|
||||
|
||||
def __private_method(self) -> Any: ...
|
||||
|
||||
def _protected_method(self) -> Any: ...
|
||||
|
||||
def usual_method(self) -> Any: ...
|
||||
|
||||
def __hash__(self) -> int: ... # basically any magic (or look-alike) method
|
||||
```
|
||||
|
||||
Methods inside one group should be ordered alphabetically, the only exception is `__init__` method (`__post__init__` for dataclasses) which should be defined first. For test methods it is recommended to follow the order in which functions are defined.
|
||||
|
||||
Though, we would like to highlight abstract methods (i.e. ones which raise `NotImplementedError`), we still keep in global order at the moment.
|
||||
|
||||
* Abstract methods must raise `NotImplementedError` instead of using `abc.abstractmethod`. The reason behind this restriction is the fact that we have class/static abstract methods for those we need to define their attribute first making the code harder to read.
|
||||
* For any path interactions `pathlib.Path` must be used.
|
||||
* Configuration interactions must go through `ahriman.core.configuration.Configuration` class instance.
|
||||
* In case if class load requires some actions, it is recommended to create class method which can be used for class instantiating.
|
||||
@ -59,9 +147,9 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines.
|
||||
* It is possible to create file which contains some functions (e.g. `ahriman.core.util`), but in this case you would need to define `__all__` attribute.
|
||||
* The file size mentioned above must be applicable in general. In case of big classes consider splitting them into traits. Note, however, that `pylint` includes comments and docstrings into counter, thus you need to check file size by other tools.
|
||||
* No global variable is allowed outside of `ahriman.version` module.
|
||||
* No global variable is allowed outside of `ahriman.version` module. `ahriman.core.context` is also special case.
|
||||
* Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent.
|
||||
* If your class writes anything to log, the `ahriman.core.lazy_logging.LazyLogging` trait must be used.
|
||||
* If your class writes anything to log, the `ahriman.core.log.LazyLogging` trait must be used.
|
||||
|
||||
### Other checks
|
||||
|
||||
|
@ -10,6 +10,7 @@ ENV AHRIMAN_PACKAGER="ahriman bot <ahriman@example.com>"
|
||||
ENV AHRIMAN_PORT=""
|
||||
ENV AHRIMAN_REPOSITORY="aur-clone"
|
||||
ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman"
|
||||
ENV AHRIMAN_UNIX_SOCKET=""
|
||||
ENV AHRIMAN_USER="ahriman"
|
||||
|
||||
# install environment
|
||||
@ -26,7 +27,7 @@ COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
|
||||
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
|
||||
RUN pacman --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-requests python-setuptools python-srcinfo && \
|
||||
pacman --noconfirm -Sy python-build python-installer python-wheel && \
|
||||
pacman --noconfirm -Sy breezy mercurial python-aiohttp python-boto3 python-cryptography python-jinja rsync subversion && \
|
||||
pacman --noconfirm -Sy breezy mercurial python-aiohttp python-boto3 python-cryptography python-jinja python-requests-unixsocket rsync subversion && \
|
||||
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-jinja2 python-aiohttp-debugtoolbar \
|
||||
python-aiohttp-session python-aiohttp-security
|
||||
|
||||
@ -50,4 +51,4 @@ VOLUME ["/var/lib/ahriman"]
|
||||
COPY "docker/entrypoint.sh" "/usr/local/bin/entrypoint"
|
||||
ENTRYPOINT ["entrypoint"]
|
||||
# default command
|
||||
CMD ["repo-update"]
|
||||
CMD ["repo-update", "--refresh"]
|
||||
|
4
Makefile
4
Makefile
@ -3,7 +3,7 @@
|
||||
|
||||
PROJECT := ahriman
|
||||
|
||||
FILES := AUTHORS CONTRIBUTING.md COPYING README.md docs package src setup.py tox.ini web.png
|
||||
FILES := AUTHORS CONTRIBUTING.md COPYING Makefile README.md SECURITY.md docs package src setup.py tox.ini web.png
|
||||
TARGET_FILES := $(addprefix $(PROJECT)/, $(FILES))
|
||||
IGNORE_FILES := package/archlinux src/.mypy_cache
|
||||
|
||||
@ -38,7 +38,7 @@ html: specification
|
||||
tox -e docs-html
|
||||
|
||||
push: specification archlinux
|
||||
git add package/archlinux/PKGBUILD src/ahriman/version.py docs/ahriman-architecture.svg docs/ahriman.1
|
||||
git add package/archlinux/PKGBUILD src/ahriman/version.py docs/ahriman-architecture.svg docs/ahriman.1 docs/completions/
|
||||
git commit -m "Release $(VERSION)"
|
||||
git tag "$(VERSION)"
|
||||
git push
|
||||
|
@ -33,3 +33,9 @@ Every available option is described in the [documentation](https://ahriman.readt
|
||||
The application provides reasonable defaults which allow to use it out-of-box; however additional steps (like configuring build toolchain and sudoers) are recommended and can be easily achieved by following install instructions.
|
||||
|
||||
## [FAQ](https://ahriman.readthedocs.io/en/latest/faq.html)
|
||||
|
||||
## Live demos
|
||||
|
||||
* [Build status page](https://ahriman-demo.arcanis.me). You can log in as `demo` user by using `demo` password. However, you will not be able to run tasks.
|
||||
* [Repository index](http://repo.arcanis.me/x86_64/index.html).
|
||||
* [Telegram feed](https://t.me/arcanisrepo).
|
||||
|
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The project follows bleeding edge philosophy, thus only the latest version is supported with the exception for release candidates (i.e. tags which are marked with `rc` suffix).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
In the most cases you can report (suspected) security vulnerabilities directly on github by using ["Security report" template](https://github.com/arcan1s/ahriman/issues/new?assignees=&labels=security&template=02-security-report.md&title=). However, if your report could lead to data leak or break the system we kindly ask you to contact [current maintainer](AUTHORS) directly by email.
|
@ -4,9 +4,17 @@ set -e
|
||||
[ -n "$AHRIMAN_DEBUG" ] && set -x
|
||||
|
||||
# configuration tune
|
||||
sed -i "s|root = /var/lib/ahriman|root = $AHRIMAN_REPOSITORY_ROOT|g" "/etc/ahriman.ini"
|
||||
sed -i "s|database = /var/lib/ahriman/ahriman.db|database = $AHRIMAN_REPOSITORY_ROOT/ahriman.db|g" "/etc/ahriman.ini"
|
||||
sed -i "s|host = 127.0.0.1|host = $AHRIMAN_HOST|g" "/etc/ahriman.ini"
|
||||
cat <<EOF > "/etc/ahriman.ini.d/00-docker.ini"
|
||||
[repository]
|
||||
root = $AHRIMAN_REPOSITORY_ROOT
|
||||
|
||||
[settings]
|
||||
database = $AHRIMAN_REPOSITORY_ROOT/ahriman.db
|
||||
|
||||
[web]
|
||||
host = $AHRIMAN_HOST
|
||||
|
||||
EOF
|
||||
sed -i "s|handlers = syslog_handler|handlers = ${AHRIMAN_OUTPUT}_handler|g" "/etc/ahriman.ini.d/logging.ini"
|
||||
|
||||
AHRIMAN_DEFAULT_ARGS=("--architecture" "$AHRIMAN_ARCHITECTURE")
|
||||
@ -22,18 +30,23 @@ fi
|
||||
[ -d "$AHRIMAN_REPOSITORY_ROOT" ] || mkdir "$AHRIMAN_REPOSITORY_ROOT"
|
||||
chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_REPOSITORY_ROOT"
|
||||
|
||||
# create .gnupg directory which is required for keys
|
||||
AHRIMAN_GNUPG_HOME="$(getent passwd "$AHRIMAN_USER" | cut -d : -f 6)/.gnupg"
|
||||
[ -d "$AHRIMAN_GNUPG_HOME" ] || mkdir -m700 "$AHRIMAN_GNUPG_HOME"
|
||||
chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_GNUPG_HOME"
|
||||
|
||||
# run built-in setup command
|
||||
AHRIMAN_SETUP_ARGS=("--build-as-user" "$AHRIMAN_USER")
|
||||
AHRIMAN_SETUP_ARGS+=("--packager" "$AHRIMAN_PACKAGER")
|
||||
AHRIMAN_SETUP_ARGS+=("--repository" "$AHRIMAN_REPOSITORY")
|
||||
if [ -n "$AHRIMAN_PORT" ]; then
|
||||
# in addition it must be handled in docker run command
|
||||
AHRIMAN_SETUP_ARGS+=("--web-port" "$AHRIMAN_PORT")
|
||||
fi
|
||||
if [ -n "$AHRIMAN_UNIX_SOCKET" ]; then
|
||||
AHRIMAN_SETUP_ARGS+=("--web-unix-socket" "$AHRIMAN_UNIX_SOCKET")
|
||||
fi
|
||||
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-setup "${AHRIMAN_SETUP_ARGS[@]}"
|
||||
|
||||
# refresh database
|
||||
pacman -Syy &> /dev/null
|
||||
# create machine-id which is required by build tools
|
||||
systemd-machine-id-setup &> /dev/null
|
||||
|
||||
|
@ -11,8 +11,8 @@ Depending on the goal the package can be used in different ways. Nevertheless, i
|
||||
from ahriman.core.database import SQLite
|
||||
|
||||
architecture = "x86_64"
|
||||
configuration = Configuration.from_path(Path("/etc/ahriman.ini"), architecture, quiet=False)
|
||||
sqlite = SQLite.load(configuration)
|
||||
configuration = Configuration.from_path(Path("/etc/ahriman.ini"), architecture)
|
||||
database = SQLite.load(configuration)
|
||||
|
||||
At this point there are ``configuration`` and ``database`` instances which can be used later at any time anywhere, e.g.
|
||||
|
||||
@ -27,7 +27,7 @@ Almost all actions are wrapped by ``ahriman.core.repository.Repository`` class
|
||||
|
||||
from ahriman.core.repository import Repository
|
||||
|
||||
repository = Repository(architecture, configuration, database, no_report=False, unsafe=False)
|
||||
repository = Repository(architecture, configuration, database, report=True, unsafe=False)
|
||||
|
||||
And the ``repository`` instance can be used to perform repository maintenance
|
||||
|
||||
@ -37,6 +37,6 @@ And the ``repository`` instance can be used to perform repository maintenance
|
||||
built_packages = repository.packages_built()
|
||||
update_result = repository.process_update(built_packages)
|
||||
|
||||
repository.process_triggers(update_result)
|
||||
repository.triggers.on_result(update_result, repository.packages())
|
||||
|
||||
For the more info please refer to the classes documentation.
|
||||
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 613 KiB After Width: | Height: | Size: 641 KiB |
@ -1,16 +1,16 @@
|
||||
.TH AHRIMAN "1" "2022\-11\-11" "ahriman" "Generated Python Manual"
|
||||
.TH AHRIMAN "1" "2023\-01\-03" "ahriman" "Generated Python Manual"
|
||||
.SH NAME
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
.B ahriman
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--report | --no-report] [-q] [--unsafe] [-V] {aur-search,search,daemon,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-triggers,repo-update,update,shell,user-add,user-list,user-remove,version,web} ...
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--report | --no-report] [-q] [--unsafe] [-V] {aur-search,search,daemon,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,shell,user-add,user-list,user-remove,version,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)
|
||||
target architectures. For several subcommands it can be used multiple times
|
||||
|
||||
.TP
|
||||
\fB\-c\fR \fI\,CONFIGURATION\/\fR, \fB\-\-configuration\fR \fI\,CONFIGURATION\/\fR
|
||||
@ -121,6 +121,9 @@ update repository status
|
||||
\fBahriman\fR \fI\,repo\-sync\/\fR
|
||||
sync repository
|
||||
.TP
|
||||
\fBahriman\fR \fI\,repo\-tree\/\fR
|
||||
dump repository tree
|
||||
.TP
|
||||
\fBahriman\fR \fI\,repo\-triggers\/\fR
|
||||
run triggers
|
||||
.TP
|
||||
@ -128,7 +131,7 @@ run triggers
|
||||
update packages
|
||||
.TP
|
||||
\fBahriman\fR \fI\,shell\/\fR
|
||||
envoke python shell
|
||||
invoke python shell
|
||||
.TP
|
||||
\fBahriman\fR \fI\,user\-add\/\fR
|
||||
create or update user
|
||||
@ -277,7 +280,8 @@ remove package from the repository
|
||||
package name or base
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-status'\/\fR
|
||||
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-i] [\-s {unknown,pending,building,failed,success}] [package ...]
|
||||
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}]
|
||||
[package ...]
|
||||
|
||||
request status of the package
|
||||
|
||||
@ -295,8 +299,8 @@ get service status itself
|
||||
return non\-zero exit status if result is empty
|
||||
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-info\fR
|
||||
show additional package information
|
||||
\fB\-\-info\fR, \fB\-\-no\-info\fR
|
||||
show additional package information (default: False)
|
||||
|
||||
.TP
|
||||
\fB\-s\fR \fI\,{unknown,pending,building,failed,success}\/\fR, \fB\-\-status\fR \fI\,{unknown,pending,building,failed,success}\/\fR
|
||||
@ -507,9 +511,10 @@ root path of the extracted files
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-setup'\/\fR
|
||||
usage: ahriman repo\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-build\-command BUILD_COMMAND]
|
||||
[\-\-from\-configuration FROM_CONFIGURATION] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER
|
||||
\-\-repository REPOSITORY [\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,pacakges,repository}]
|
||||
[\-\-web\-port WEB_PORT]
|
||||
[\-\-from\-configuration FROM_CONFIGURATION] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
|
||||
[\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER \-\-repository REPOSITORY [\-\-sign\-key SIGN_KEY]
|
||||
[\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
|
||||
[\-\-web\-unix\-socket WEB_UNIX_SOCKET]
|
||||
|
||||
create initial service configuration, requires root
|
||||
|
||||
@ -526,6 +531,10 @@ build command prefix
|
||||
\fB\-\-from\-configuration\fR \fI\,FROM_CONFIGURATION\/\fR
|
||||
path to default devtools pacman configuration
|
||||
|
||||
.TP
|
||||
\fB\-\-makeflags\-jobs\fR, \fB\-\-no\-makeflags\-jobs\fR
|
||||
append MAKEFLAGS variable with parallelism set to number of cores (default: True)
|
||||
|
||||
.TP
|
||||
\fB\-\-multilib\fR, \fB\-\-no\-multilib\fR
|
||||
add or do not multilib repository (default: True)
|
||||
@ -543,13 +552,17 @@ repository name
|
||||
sign key id
|
||||
|
||||
.TP
|
||||
\fB\-\-sign\-target\fR \fI\,{disabled,pacakges,repository}\/\fR
|
||||
\fB\-\-sign\-target\fR \fI\,{disabled,packages,repository}\/\fR
|
||||
sign options
|
||||
|
||||
.TP
|
||||
\fB\-\-web\-port\fR \fI\,WEB_PORT\/\fR
|
||||
port of the web service
|
||||
|
||||
.TP
|
||||
\fB\-\-web\-unix\-socket\fR \fI\,WEB_UNIX_SOCKET\/\fR
|
||||
path to unix socket used for interprocess communications
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-sign'\/\fR
|
||||
usage: ahriman repo\-sign [\-h] [package ...]
|
||||
|
||||
@ -574,6 +587,11 @@ usage: ahriman repo\-sync [\-h]
|
||||
|
||||
sync repository files to remote server according to current settings
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-tree'\/\fR
|
||||
usage: ahriman repo\-tree [\-h]
|
||||
|
||||
dump repository tree based on packages dependencies
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-triggers'\/\fR
|
||||
usage: ahriman repo\-triggers [\-h] [trigger ...]
|
||||
|
||||
@ -581,7 +599,7 @@ run triggers on empty build result as configured by settings
|
||||
|
||||
.TP
|
||||
\fBtrigger\fR
|
||||
instead of running all triggers as set by configuration, just process specified ones oin order of metion
|
||||
instead of running all triggers as set by configuration, just process specified ones in order of mention
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR
|
||||
usage: ahriman repo\-update [\-h] [\-\-dry\-run] [\-e] [\-\-aur | \-\-no\-aur] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual]
|
||||
@ -633,7 +651,7 @@ drop into python shell while having created application
|
||||
instead of dropping into shell, just execute the specified code
|
||||
|
||||
.SH COMMAND \fI\,'ahriman user\-add'\/\fR
|
||||
usage: ahriman user\-add [\-h] [\-\-as\-service] [\-p PASSWORD] [\-r {unauthorized,read,reporter,full}] [\-s] username
|
||||
usage: ahriman user\-add [\-h] [\-p PASSWORD] [\-r {unauthorized,read,reporter,full}] [\-s] username
|
||||
|
||||
update user for web services with the given password and role. In case if password was not entered it will be asked interactively
|
||||
|
||||
@ -642,10 +660,6 @@ update user for web services with the given password and role. In case if passwo
|
||||
username for web service
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman user\-add'\/\fR
|
||||
.TP
|
||||
\fB\-\-as\-service\fR
|
||||
add user as service user
|
||||
|
||||
.TP
|
||||
\fB\-p\fR \fI\,PASSWORD\/\fR, \fB\-\-password\fR \fI\,PASSWORD\/\fR
|
||||
user password. Blank password will be treated as empty password, which is in particular must be used for OAuth2
|
||||
@ -678,7 +692,7 @@ return non\-zero exit status if result is empty
|
||||
filter users by role
|
||||
|
||||
.SH COMMAND \fI\,'ahriman user\-remove'\/\fR
|
||||
usage: ahriman user\-remove [\-h] [\-s] username
|
||||
usage: ahriman user\-remove [\-h] username
|
||||
|
||||
remove user from the user mapping and update the configuration
|
||||
|
||||
@ -686,11 +700,6 @@ remove user from the user mapping and update the configuration
|
||||
\fBusername\fR
|
||||
username for web service
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman user\-remove'\/\fR
|
||||
.TP
|
||||
\fB\-s\fR, \fB\-\-secure\fR
|
||||
set file permissions to user\-only
|
||||
|
||||
.SH COMMAND \fI\,'ahriman version'\/\fR
|
||||
usage: ahriman version [\-h]
|
||||
|
||||
|
@ -156,6 +156,14 @@ ahriman.application.handlers.status\_update module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.structure module
|
||||
---------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.application.handlers.structure
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.triggers module
|
||||
--------------------------------------------
|
||||
|
||||
|
@ -36,6 +36,14 @@ ahriman.core.database.migrations.m003\_patch\_variables module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.migrations.m004\_logs module
|
||||
--------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.migrations.m004_logs
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -20,6 +20,14 @@ ahriman.core.database.operations.build\_operations module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.operations.logs\_operations module
|
||||
--------------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.operations.logs_operations
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.operations.operations module
|
||||
--------------------------------------------------
|
||||
|
||||
|
@ -68,6 +68,14 @@ ahriman.core.formatters.string\_printer module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.tree\_printer module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.formatters.tree_printer
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.update\_printer module
|
||||
----------------------------------------------
|
||||
|
||||
|
45
docs/ahriman.core.log.rst
Normal file
45
docs/ahriman.core.log.rst
Normal file
@ -0,0 +1,45 @@
|
||||
ahriman.core.log package
|
||||
========================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.core.log.filtered\_access\_logger module
|
||||
------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.log.filtered_access_logger
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.log.http\_log\_handler module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.log.http_log_handler
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.log.lazy\_logging module
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.log.lazy_logging
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.log.log module
|
||||
---------------------------
|
||||
|
||||
.. automodule:: ahriman.core.log.log
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: ahriman.core.log
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
@ -13,6 +13,7 @@ Subpackages
|
||||
ahriman.core.database
|
||||
ahriman.core.formatters
|
||||
ahriman.core.gitremote
|
||||
ahriman.core.log
|
||||
ahriman.core.report
|
||||
ahriman.core.repository
|
||||
ahriman.core.sign
|
||||
@ -39,14 +40,6 @@ ahriman.core.exceptions module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.lazy\_logging module
|
||||
---------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.lazy_logging
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.spawn module
|
||||
-------------------------
|
||||
|
||||
|
@ -36,6 +36,14 @@ ahriman.models.build\_status module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.context\_key module
|
||||
----------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.context_key
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.counters module
|
||||
------------------------------
|
||||
|
||||
@ -52,6 +60,14 @@ ahriman.models.internal\_status module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.log\_record\_id module
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.log_record_id
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.migration module
|
||||
-------------------------------
|
||||
|
||||
|
@ -12,6 +12,22 @@ ahriman.web.views.service.add module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.service.pgp module
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.service.pgp
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.service.rebuild module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.service.rebuild
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.service.remove module
|
||||
---------------------------------------
|
||||
|
||||
@ -36,6 +52,14 @@ ahriman.web.views.service.search module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.service.update module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.service.update
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -4,6 +4,14 @@ ahriman.web.views.status package
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.web.views.status.logs module
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.status.logs
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.status.package module
|
||||
---------------------------------------
|
||||
|
||||
|
@ -28,7 +28,7 @@ This package contains application (aka executable) related classes and everythin
|
||||
``ahriman.core`` package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This package contains everything which is required for any time of application run and separated into several packages:
|
||||
This package contains everything required for the most of application actions and it is separated into several packages:
|
||||
|
||||
* ``ahriman.core.alpm`` package controls pacman related functions. It provides wrappers for ``pyalpm`` library and safe calls for repository tools (``repo-add`` and ``repo-remove``). Also this package contains ``ahriman.core.alpm.remote`` package which provides wrapper for remote sources (e.g. AUR RPC and official repositories RPC).
|
||||
* ``ahriman.core.auth`` package provides classes for authorization methods used by web mostly. Base class is ``ahriman.core.auth.Auth`` which must be called by ``load`` method.
|
||||
@ -36,6 +36,7 @@ This package contains everything which is required for any time of application r
|
||||
* ``ahriman.core.database`` is everything including data and schema migrations for database.
|
||||
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
||||
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
||||
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and access logger for HTTP services with additional filters.
|
||||
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
||||
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.Repository`` class) implementation.
|
||||
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
|
||||
@ -53,7 +54,7 @@ This package also provides some generic functions and classes which may be used
|
||||
``ahriman.models`` package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It provides models for any other part of application. Unlike ``ahriman.core`` package classes from here provides only conversion methods (e.g. create class from another or convert to). Mostly case classes and enumerations.
|
||||
It provides models for any other part of application. Unlike ``ahriman.core`` package classes from here provide only conversion methods (e.g. create class from another or convert to). Mostly case classes and enumerations.
|
||||
|
||||
``ahriman.web`` package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -76,7 +77,7 @@ Application run
|
||||
* Return result (success or failure) of each subprocess and exit from application.
|
||||
* Some handlers may override their status and throw ``ExitCode`` exception. This exception is just silently suppressed and changes application exit code to ``1``.
|
||||
|
||||
In most cases handlers spawn god class ``ahriman.application.application.Application`` class and call required methods.
|
||||
In the most cases handlers spawn god class ``ahriman.application.application.Application`` class and call required methods.
|
||||
|
||||
Application is designed to run from ``systemd`` services and provides parametrized by architecture timer and service file for that.
|
||||
|
||||
@ -168,6 +169,15 @@ Utils
|
||||
|
||||
For every external command run (which is actually not recommended if possible) custom wrapper for ``subprocess`` is used. Additional functions ``ahriman.core.auth.helpers`` provide safe calls for ``aiohttp_security`` methods and are required to make this dependency optional.
|
||||
|
||||
Context variables
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Package provides implicit global variables which can be accessed from ``ahriman.core`` package as ``context`` variable, wrapped by ``contextvars.ContextVar`` class. The value of the variable is defaulting to private ``_Context`` class which is defined in the same module. The default values - such as ``database`` and ``sign`` - are being set on the service initialization.
|
||||
|
||||
The ``_Context`` class itself mimics default collection interface (as is Mapping) and can be modified by ``_Context.set`` method. The stored variables can be achieved by ``_Context.get`` method, which is unlike default ``Mapping`` interface also performs type and presence checks.
|
||||
|
||||
In order to provide statically typed interface, the ``ahriman.models.context_key.ContextKey`` class is used for both ``_Content.get`` and ``_Content.set`` methods; the context instance itself, however, does not store information about types.
|
||||
|
||||
Submodules
|
||||
^^^^^^^^^^
|
||||
|
||||
@ -196,14 +206,16 @@ means that there is user ``username`` with ``read`` access and password ``passwo
|
||||
|
||||
OAuth provider uses library definitions (``aioauth-client``) in order *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
|
||||
|
||||
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature is used by service reporting (aka robots).
|
||||
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature can be used by service reporting (aka robots).
|
||||
|
||||
In addition, web service checks the source socket used. In case if it belongs to ``socket.AF_UNIX`` family, it will skip any furher checks considering the request to be performed in safe environment (e.g. on the same physical machine). This feature, in particular is being used by the reporter instances in case if socket address is set in configuration.
|
||||
|
||||
In order to configure users there are special commands.
|
||||
|
||||
Triggers
|
||||
^^^^^^^^
|
||||
|
||||
Triggers are extensions which can be used in order to perform any actions on application start, after the update process and, finally, before the application exit. The package provides two default extensions - one is report generation and another one is remote upload feature.
|
||||
Triggers are extensions which can be used in order to perform any actions on application start, after the update process and, finally, before the application exit.
|
||||
|
||||
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it.
|
||||
|
||||
@ -244,6 +256,7 @@ Web application requires the following python packages to be installed:
|
||||
* In addition, ``aiohttp_debugtoolbar`` is required for debug panel. Please note that this option does not work together with authorization and basically must not be used in production.
|
||||
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
|
||||
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
|
||||
* In addition if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket`` library is required.
|
||||
|
||||
Middlewares
|
||||
^^^^^^^^^^^
|
||||
|
484
docs/completions/bash/_ahriman
Normal file
484
docs/completions/bash/_ahriman
Normal file
@ -0,0 +1,484 @@
|
||||
# AUTOMATICALLY GENERATED by `shtab`
|
||||
|
||||
_shtab_ahriman_subparsers=('aur-search' 'search' 'daemon' 'help' 'help-commands-unsafe' 'key-import' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-clean' 'clean' 'repo-config' 'config' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-setup' 'init' 'repo-init' 'setup' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'shell' 'user-add' 'user-list' 'user-remove' 'version' 'web')
|
||||
|
||||
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--report' '--no-report' '-q' '--quiet' '--unsafe' '-V' '--version')
|
||||
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
|
||||
_shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
|
||||
_shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_help_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help' '--command')
|
||||
_shtab_ahriman_key_import_option_strings=('-h' '--help' '--key-server')
|
||||
_shtab_ahriman_package_add_option_strings=('-h' '--help' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '--without-dependencies')
|
||||
_shtab_ahriman_add_option_strings=('-h' '--help' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '--without-dependencies')
|
||||
_shtab_ahriman_package_update_option_strings=('-h' '--help' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '--without-dependencies')
|
||||
_shtab_ahriman_package_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_package_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status')
|
||||
_shtab_ahriman_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status')
|
||||
_shtab_ahriman_package_status_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_package_status_update_option_strings=('-h' '--help' '-s' '--status')
|
||||
_shtab_ahriman_status_update_option_strings=('-h' '--help' '-s' '--status')
|
||||
_shtab_ahriman_patch_add_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_patch_list_option_strings=('-h' '--help' '-e' '--exit-code' '-v' '--variable')
|
||||
_shtab_ahriman_patch_remove_option_strings=('-h' '--help' '-v' '--variable')
|
||||
_shtab_ahriman_patch_set_add_option_strings=('-h' '--help' '-t' '--track')
|
||||
_shtab_ahriman_repo_backup_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_repo_config_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_config_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code')
|
||||
_shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code')
|
||||
_shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run')
|
||||
_shtab_ahriman_remove_unknown_option_strings=('-h' '--help' '--dry-run')
|
||||
_shtab_ahriman_repo_report_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_report_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_restore_option_strings=('-h' '--help' '-o' '--output')
|
||||
_shtab_ahriman_repo_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--makeflags-jobs' '--no-makeflags-jobs' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_init_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--makeflags-jobs' '--no-makeflags-jobs' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_repo_init_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--makeflags-jobs' '--no-makeflags-jobs' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--makeflags-jobs' '--no-makeflags-jobs' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_repo_sign_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_sign_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_status_update_option_strings=('-h' '--help' '-s' '--status')
|
||||
_shtab_ahriman_repo_sync_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_sync_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_tree_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_triggers_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_update_option_strings=('-h' '--help' '--dry-run' '-e' '--exit-code' '--aur' '--no-aur' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_update_option_strings=('-h' '--help' '--dry-run' '-e' '--exit-code' '--aur' '--no-aur' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_shell_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_user_add_option_strings=('-h' '--help' '-p' '--password' '-r' '--role' '-s' '--secure')
|
||||
_shtab_ahriman_user_list_option_strings=('-h' '--help' '-e' '--exit-code' '-r' '--role')
|
||||
_shtab_ahriman_user_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_version_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_web_option_strings=('-h' '--help')
|
||||
|
||||
|
||||
|
||||
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'daemon' 'help' 'help-commands-unsafe' 'key-import' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-clean' 'clean' 'repo-config' 'config' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-setup' 'init' 'repo-init' 'setup' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'shell' 'user-add' 'user-list' 'user-remove' 'version' 'web')
|
||||
_shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'url' 'url_path' 'version')
|
||||
_shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'url' 'url_path' 'version')
|
||||
_shtab_ahriman_package_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
_shtab_ahriman_package_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
_shtab_ahriman_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
_shtab_ahriman_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
_shtab_ahriman_package_update__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
_shtab_ahriman_package_update___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
_shtab_ahriman_package_status__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_package_status___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_status__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_status___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_package_status_update__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_package_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_status_update__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_repo_setup___sign_target_choices=('disabled' 'packages' 'repository')
|
||||
_shtab_ahriman_init___sign_target_choices=('disabled' 'packages' 'repository')
|
||||
_shtab_ahriman_repo_init___sign_target_choices=('disabled' 'packages' 'repository')
|
||||
_shtab_ahriman_setup___sign_target_choices=('disabled' 'packages' 'repository')
|
||||
_shtab_ahriman_repo_status_update__s_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_repo_status_update___status_choices=('unknown' 'pending' 'building' 'failed' 'success')
|
||||
_shtab_ahriman_user_add__r_choices=('unauthorized' 'read' 'reporter' 'full')
|
||||
_shtab_ahriman_user_add___role_choices=('unauthorized' 'read' 'reporter' 'full')
|
||||
_shtab_ahriman_user_list__r_choices=('unauthorized' 'read' 'reporter' 'full')
|
||||
_shtab_ahriman_user_list___role_choices=('unauthorized' 'read' 'reporter' 'full')
|
||||
|
||||
_shtab_ahriman_pos_0_nargs=A...
|
||||
_shtab_ahriman__h_nargs=0
|
||||
_shtab_ahriman___help_nargs=0
|
||||
_shtab_ahriman___force_nargs=0
|
||||
_shtab_ahriman___report_nargs=0
|
||||
_shtab_ahriman___no_report_nargs=0
|
||||
_shtab_ahriman__q_nargs=0
|
||||
_shtab_ahriman___quiet_nargs=0
|
||||
_shtab_ahriman___unsafe_nargs=0
|
||||
_shtab_ahriman__V_nargs=0
|
||||
_shtab_ahriman___version_nargs=0
|
||||
_shtab_ahriman_aur_search_pos_0_nargs=+
|
||||
_shtab_ahriman_aur_search__h_nargs=0
|
||||
_shtab_ahriman_aur_search___help_nargs=0
|
||||
_shtab_ahriman_aur_search__e_nargs=0
|
||||
_shtab_ahriman_aur_search___exit_code_nargs=0
|
||||
_shtab_ahriman_aur_search___info_nargs=0
|
||||
_shtab_ahriman_aur_search___no_info_nargs=0
|
||||
_shtab_ahriman_search_pos_0_nargs=+
|
||||
_shtab_ahriman_search__h_nargs=0
|
||||
_shtab_ahriman_search___help_nargs=0
|
||||
_shtab_ahriman_search__e_nargs=0
|
||||
_shtab_ahriman_search___exit_code_nargs=0
|
||||
_shtab_ahriman_search___info_nargs=0
|
||||
_shtab_ahriman_search___no_info_nargs=0
|
||||
_shtab_ahriman_daemon__h_nargs=0
|
||||
_shtab_ahriman_daemon___help_nargs=0
|
||||
_shtab_ahriman_daemon___aur_nargs=0
|
||||
_shtab_ahriman_daemon___no_aur_nargs=0
|
||||
_shtab_ahriman_daemon___local_nargs=0
|
||||
_shtab_ahriman_daemon___no_local_nargs=0
|
||||
_shtab_ahriman_daemon___manual_nargs=0
|
||||
_shtab_ahriman_daemon___no_manual_nargs=0
|
||||
_shtab_ahriman_daemon___vcs_nargs=0
|
||||
_shtab_ahriman_daemon___no_vcs_nargs=0
|
||||
_shtab_ahriman_daemon__y_nargs=0
|
||||
_shtab_ahriman_daemon___refresh_nargs=0
|
||||
_shtab_ahriman_help__h_nargs=0
|
||||
_shtab_ahriman_help___help_nargs=0
|
||||
_shtab_ahriman_help_commands_unsafe__h_nargs=0
|
||||
_shtab_ahriman_help_commands_unsafe___help_nargs=0
|
||||
_shtab_ahriman_key_import__h_nargs=0
|
||||
_shtab_ahriman_key_import___help_nargs=0
|
||||
_shtab_ahriman_package_add_pos_0_nargs=+
|
||||
_shtab_ahriman_package_add__h_nargs=0
|
||||
_shtab_ahriman_package_add___help_nargs=0
|
||||
_shtab_ahriman_package_add__e_nargs=0
|
||||
_shtab_ahriman_package_add___exit_code_nargs=0
|
||||
_shtab_ahriman_package_add__n_nargs=0
|
||||
_shtab_ahriman_package_add___now_nargs=0
|
||||
_shtab_ahriman_package_add__y_nargs=0
|
||||
_shtab_ahriman_package_add___refresh_nargs=0
|
||||
_shtab_ahriman_package_add___without_dependencies_nargs=0
|
||||
_shtab_ahriman_add_pos_0_nargs=+
|
||||
_shtab_ahriman_add__h_nargs=0
|
||||
_shtab_ahriman_add___help_nargs=0
|
||||
_shtab_ahriman_add__e_nargs=0
|
||||
_shtab_ahriman_add___exit_code_nargs=0
|
||||
_shtab_ahriman_add__n_nargs=0
|
||||
_shtab_ahriman_add___now_nargs=0
|
||||
_shtab_ahriman_add__y_nargs=0
|
||||
_shtab_ahriman_add___refresh_nargs=0
|
||||
_shtab_ahriman_add___without_dependencies_nargs=0
|
||||
_shtab_ahriman_package_update_pos_0_nargs=+
|
||||
_shtab_ahriman_package_update__h_nargs=0
|
||||
_shtab_ahriman_package_update___help_nargs=0
|
||||
_shtab_ahriman_package_update__e_nargs=0
|
||||
_shtab_ahriman_package_update___exit_code_nargs=0
|
||||
_shtab_ahriman_package_update__n_nargs=0
|
||||
_shtab_ahriman_package_update___now_nargs=0
|
||||
_shtab_ahriman_package_update__y_nargs=0
|
||||
_shtab_ahriman_package_update___refresh_nargs=0
|
||||
_shtab_ahriman_package_update___without_dependencies_nargs=0
|
||||
_shtab_ahriman_package_remove_pos_0_nargs=+
|
||||
_shtab_ahriman_package_remove__h_nargs=0
|
||||
_shtab_ahriman_package_remove___help_nargs=0
|
||||
_shtab_ahriman_remove_pos_0_nargs=+
|
||||
_shtab_ahriman_remove__h_nargs=0
|
||||
_shtab_ahriman_remove___help_nargs=0
|
||||
_shtab_ahriman_package_status_pos_0_nargs=*
|
||||
_shtab_ahriman_package_status__h_nargs=0
|
||||
_shtab_ahriman_package_status___help_nargs=0
|
||||
_shtab_ahriman_package_status___ahriman_nargs=0
|
||||
_shtab_ahriman_package_status__e_nargs=0
|
||||
_shtab_ahriman_package_status___exit_code_nargs=0
|
||||
_shtab_ahriman_package_status___info_nargs=0
|
||||
_shtab_ahriman_package_status___no_info_nargs=0
|
||||
_shtab_ahriman_status_pos_0_nargs=*
|
||||
_shtab_ahriman_status__h_nargs=0
|
||||
_shtab_ahriman_status___help_nargs=0
|
||||
_shtab_ahriman_status___ahriman_nargs=0
|
||||
_shtab_ahriman_status__e_nargs=0
|
||||
_shtab_ahriman_status___exit_code_nargs=0
|
||||
_shtab_ahriman_status___info_nargs=0
|
||||
_shtab_ahriman_status___no_info_nargs=0
|
||||
_shtab_ahriman_package_status_remove_pos_0_nargs=+
|
||||
_shtab_ahriman_package_status_remove__h_nargs=0
|
||||
_shtab_ahriman_package_status_remove___help_nargs=0
|
||||
_shtab_ahriman_package_status_update_pos_0_nargs=*
|
||||
_shtab_ahriman_package_status_update__h_nargs=0
|
||||
_shtab_ahriman_package_status_update___help_nargs=0
|
||||
_shtab_ahriman_status_update_pos_0_nargs=*
|
||||
_shtab_ahriman_status_update__h_nargs=0
|
||||
_shtab_ahriman_status_update___help_nargs=0
|
||||
_shtab_ahriman_patch_add__h_nargs=0
|
||||
_shtab_ahriman_patch_add___help_nargs=0
|
||||
_shtab_ahriman_patch_list__h_nargs=0
|
||||
_shtab_ahriman_patch_list___help_nargs=0
|
||||
_shtab_ahriman_patch_list__e_nargs=0
|
||||
_shtab_ahriman_patch_list___exit_code_nargs=0
|
||||
_shtab_ahriman_patch_remove__h_nargs=0
|
||||
_shtab_ahriman_patch_remove___help_nargs=0
|
||||
_shtab_ahriman_patch_set_add__h_nargs=0
|
||||
_shtab_ahriman_patch_set_add___help_nargs=0
|
||||
_shtab_ahriman_repo_backup__h_nargs=0
|
||||
_shtab_ahriman_repo_backup___help_nargs=0
|
||||
_shtab_ahriman_repo_check_pos_0_nargs=*
|
||||
_shtab_ahriman_repo_check__h_nargs=0
|
||||
_shtab_ahriman_repo_check___help_nargs=0
|
||||
_shtab_ahriman_repo_check__e_nargs=0
|
||||
_shtab_ahriman_repo_check___exit_code_nargs=0
|
||||
_shtab_ahriman_repo_check___vcs_nargs=0
|
||||
_shtab_ahriman_repo_check___no_vcs_nargs=0
|
||||
_shtab_ahriman_repo_check__y_nargs=0
|
||||
_shtab_ahriman_repo_check___refresh_nargs=0
|
||||
_shtab_ahriman_check_pos_0_nargs=*
|
||||
_shtab_ahriman_check__h_nargs=0
|
||||
_shtab_ahriman_check___help_nargs=0
|
||||
_shtab_ahriman_check__e_nargs=0
|
||||
_shtab_ahriman_check___exit_code_nargs=0
|
||||
_shtab_ahriman_check___vcs_nargs=0
|
||||
_shtab_ahriman_check___no_vcs_nargs=0
|
||||
_shtab_ahriman_check__y_nargs=0
|
||||
_shtab_ahriman_check___refresh_nargs=0
|
||||
_shtab_ahriman_repo_clean__h_nargs=0
|
||||
_shtab_ahriman_repo_clean___help_nargs=0
|
||||
_shtab_ahriman_repo_clean___cache_nargs=0
|
||||
_shtab_ahriman_repo_clean___no_cache_nargs=0
|
||||
_shtab_ahriman_repo_clean___chroot_nargs=0
|
||||
_shtab_ahriman_repo_clean___no_chroot_nargs=0
|
||||
_shtab_ahriman_repo_clean___manual_nargs=0
|
||||
_shtab_ahriman_repo_clean___no_manual_nargs=0
|
||||
_shtab_ahriman_repo_clean___packages_nargs=0
|
||||
_shtab_ahriman_repo_clean___no_packages_nargs=0
|
||||
_shtab_ahriman_repo_clean___pacman_nargs=0
|
||||
_shtab_ahriman_repo_clean___no_pacman_nargs=0
|
||||
_shtab_ahriman_clean__h_nargs=0
|
||||
_shtab_ahriman_clean___help_nargs=0
|
||||
_shtab_ahriman_clean___cache_nargs=0
|
||||
_shtab_ahriman_clean___no_cache_nargs=0
|
||||
_shtab_ahriman_clean___chroot_nargs=0
|
||||
_shtab_ahriman_clean___no_chroot_nargs=0
|
||||
_shtab_ahriman_clean___manual_nargs=0
|
||||
_shtab_ahriman_clean___no_manual_nargs=0
|
||||
_shtab_ahriman_clean___packages_nargs=0
|
||||
_shtab_ahriman_clean___no_packages_nargs=0
|
||||
_shtab_ahriman_clean___pacman_nargs=0
|
||||
_shtab_ahriman_clean___no_pacman_nargs=0
|
||||
_shtab_ahriman_repo_config__h_nargs=0
|
||||
_shtab_ahriman_repo_config___help_nargs=0
|
||||
_shtab_ahriman_config__h_nargs=0
|
||||
_shtab_ahriman_config___help_nargs=0
|
||||
_shtab_ahriman_repo_rebuild__h_nargs=0
|
||||
_shtab_ahriman_repo_rebuild___help_nargs=0
|
||||
_shtab_ahriman_repo_rebuild___dry_run_nargs=0
|
||||
_shtab_ahriman_repo_rebuild___from_database_nargs=0
|
||||
_shtab_ahriman_repo_rebuild__e_nargs=0
|
||||
_shtab_ahriman_repo_rebuild___exit_code_nargs=0
|
||||
_shtab_ahriman_rebuild__h_nargs=0
|
||||
_shtab_ahriman_rebuild___help_nargs=0
|
||||
_shtab_ahriman_rebuild___dry_run_nargs=0
|
||||
_shtab_ahriman_rebuild___from_database_nargs=0
|
||||
_shtab_ahriman_rebuild__e_nargs=0
|
||||
_shtab_ahriman_rebuild___exit_code_nargs=0
|
||||
_shtab_ahriman_repo_remove_unknown__h_nargs=0
|
||||
_shtab_ahriman_repo_remove_unknown___help_nargs=0
|
||||
_shtab_ahriman_repo_remove_unknown___dry_run_nargs=0
|
||||
_shtab_ahriman_remove_unknown__h_nargs=0
|
||||
_shtab_ahriman_remove_unknown___help_nargs=0
|
||||
_shtab_ahriman_remove_unknown___dry_run_nargs=0
|
||||
_shtab_ahriman_repo_report__h_nargs=0
|
||||
_shtab_ahriman_repo_report___help_nargs=0
|
||||
_shtab_ahriman_report__h_nargs=0
|
||||
_shtab_ahriman_report___help_nargs=0
|
||||
_shtab_ahriman_repo_restore__h_nargs=0
|
||||
_shtab_ahriman_repo_restore___help_nargs=0
|
||||
_shtab_ahriman_repo_setup__h_nargs=0
|
||||
_shtab_ahriman_repo_setup___help_nargs=0
|
||||
_shtab_ahriman_repo_setup___makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_repo_setup___no_makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_repo_setup___multilib_nargs=0
|
||||
_shtab_ahriman_repo_setup___no_multilib_nargs=0
|
||||
_shtab_ahriman_init__h_nargs=0
|
||||
_shtab_ahriman_init___help_nargs=0
|
||||
_shtab_ahriman_init___makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_init___no_makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_init___multilib_nargs=0
|
||||
_shtab_ahriman_init___no_multilib_nargs=0
|
||||
_shtab_ahriman_repo_init__h_nargs=0
|
||||
_shtab_ahriman_repo_init___help_nargs=0
|
||||
_shtab_ahriman_repo_init___makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_repo_init___no_makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_repo_init___multilib_nargs=0
|
||||
_shtab_ahriman_repo_init___no_multilib_nargs=0
|
||||
_shtab_ahriman_setup__h_nargs=0
|
||||
_shtab_ahriman_setup___help_nargs=0
|
||||
_shtab_ahriman_setup___makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_setup___no_makeflags_jobs_nargs=0
|
||||
_shtab_ahriman_setup___multilib_nargs=0
|
||||
_shtab_ahriman_setup___no_multilib_nargs=0
|
||||
_shtab_ahriman_repo_sign_pos_0_nargs=*
|
||||
_shtab_ahriman_repo_sign__h_nargs=0
|
||||
_shtab_ahriman_repo_sign___help_nargs=0
|
||||
_shtab_ahriman_sign_pos_0_nargs=*
|
||||
_shtab_ahriman_sign__h_nargs=0
|
||||
_shtab_ahriman_sign___help_nargs=0
|
||||
_shtab_ahriman_repo_status_update__h_nargs=0
|
||||
_shtab_ahriman_repo_status_update___help_nargs=0
|
||||
_shtab_ahriman_repo_sync__h_nargs=0
|
||||
_shtab_ahriman_repo_sync___help_nargs=0
|
||||
_shtab_ahriman_sync__h_nargs=0
|
||||
_shtab_ahriman_sync___help_nargs=0
|
||||
_shtab_ahriman_repo_tree__h_nargs=0
|
||||
_shtab_ahriman_repo_tree___help_nargs=0
|
||||
_shtab_ahriman_repo_triggers_pos_0_nargs=*
|
||||
_shtab_ahriman_repo_triggers__h_nargs=0
|
||||
_shtab_ahriman_repo_triggers___help_nargs=0
|
||||
_shtab_ahriman_repo_update_pos_0_nargs=*
|
||||
_shtab_ahriman_repo_update__h_nargs=0
|
||||
_shtab_ahriman_repo_update___help_nargs=0
|
||||
_shtab_ahriman_repo_update___dry_run_nargs=0
|
||||
_shtab_ahriman_repo_update__e_nargs=0
|
||||
_shtab_ahriman_repo_update___exit_code_nargs=0
|
||||
_shtab_ahriman_repo_update___aur_nargs=0
|
||||
_shtab_ahriman_repo_update___no_aur_nargs=0
|
||||
_shtab_ahriman_repo_update___local_nargs=0
|
||||
_shtab_ahriman_repo_update___no_local_nargs=0
|
||||
_shtab_ahriman_repo_update___manual_nargs=0
|
||||
_shtab_ahriman_repo_update___no_manual_nargs=0
|
||||
_shtab_ahriman_repo_update___vcs_nargs=0
|
||||
_shtab_ahriman_repo_update___no_vcs_nargs=0
|
||||
_shtab_ahriman_repo_update__y_nargs=0
|
||||
_shtab_ahriman_repo_update___refresh_nargs=0
|
||||
_shtab_ahriman_update_pos_0_nargs=*
|
||||
_shtab_ahriman_update__h_nargs=0
|
||||
_shtab_ahriman_update___help_nargs=0
|
||||
_shtab_ahriman_update___dry_run_nargs=0
|
||||
_shtab_ahriman_update__e_nargs=0
|
||||
_shtab_ahriman_update___exit_code_nargs=0
|
||||
_shtab_ahriman_update___aur_nargs=0
|
||||
_shtab_ahriman_update___no_aur_nargs=0
|
||||
_shtab_ahriman_update___local_nargs=0
|
||||
_shtab_ahriman_update___no_local_nargs=0
|
||||
_shtab_ahriman_update___manual_nargs=0
|
||||
_shtab_ahriman_update___no_manual_nargs=0
|
||||
_shtab_ahriman_update___vcs_nargs=0
|
||||
_shtab_ahriman_update___no_vcs_nargs=0
|
||||
_shtab_ahriman_update__y_nargs=0
|
||||
_shtab_ahriman_update___refresh_nargs=0
|
||||
_shtab_ahriman_shell__h_nargs=0
|
||||
_shtab_ahriman_shell___help_nargs=0
|
||||
_shtab_ahriman_shell__v_nargs=0
|
||||
_shtab_ahriman_shell___verbose_nargs=0
|
||||
_shtab_ahriman_user_add__h_nargs=0
|
||||
_shtab_ahriman_user_add___help_nargs=0
|
||||
_shtab_ahriman_user_add__s_nargs=0
|
||||
_shtab_ahriman_user_add___secure_nargs=0
|
||||
_shtab_ahriman_user_list__h_nargs=0
|
||||
_shtab_ahriman_user_list___help_nargs=0
|
||||
_shtab_ahriman_user_list__e_nargs=0
|
||||
_shtab_ahriman_user_list___exit_code_nargs=0
|
||||
_shtab_ahriman_user_remove__h_nargs=0
|
||||
_shtab_ahriman_user_remove___help_nargs=0
|
||||
_shtab_ahriman_version__h_nargs=0
|
||||
_shtab_ahriman_version___help_nargs=0
|
||||
_shtab_ahriman_web__h_nargs=0
|
||||
_shtab_ahriman_web___help_nargs=0
|
||||
|
||||
|
||||
# $1=COMP_WORDS[1]
|
||||
_shtab_compgen_files() {
|
||||
compgen -f -- $1 # files
|
||||
}
|
||||
|
||||
# $1=COMP_WORDS[1]
|
||||
_shtab_compgen_dirs() {
|
||||
compgen -d -- $1 # recurse into subdirs
|
||||
}
|
||||
|
||||
# $1=COMP_WORDS[1]
|
||||
_shtab_replace_nonword() {
|
||||
echo "${1//[^[:word:]]/_}"
|
||||
}
|
||||
|
||||
# set default values (called for the initial parser & any subparsers)
|
||||
_set_parser_defaults() {
|
||||
local subparsers_var="${prefix}_subparsers[@]"
|
||||
sub_parsers=${!subparsers_var}
|
||||
|
||||
local current_option_strings_var="${prefix}_option_strings[@]"
|
||||
current_option_strings=${!current_option_strings_var}
|
||||
|
||||
completed_positional_actions=0
|
||||
|
||||
_set_new_action "pos_${completed_positional_actions}" true
|
||||
}
|
||||
|
||||
# $1=action identifier
|
||||
# $2=positional action (bool)
|
||||
# set all identifiers for an action's parameters
|
||||
_set_new_action() {
|
||||
current_action="${prefix}_$(_shtab_replace_nonword $1)"
|
||||
|
||||
local current_action_compgen_var=${current_action}_COMPGEN
|
||||
current_action_compgen="${!current_action_compgen_var}"
|
||||
|
||||
local current_action_choices_var="${current_action}_choices[@]"
|
||||
current_action_choices="${!current_action_choices_var}"
|
||||
|
||||
local current_action_nargs_var="${current_action}_nargs"
|
||||
if [ -n "${!current_action_nargs_var}" ]; then
|
||||
current_action_nargs="${!current_action_nargs_var}"
|
||||
else
|
||||
current_action_nargs=1
|
||||
fi
|
||||
|
||||
current_action_args_start_index=$(( $word_index + 1 ))
|
||||
|
||||
current_action_is_positional=$2
|
||||
}
|
||||
|
||||
# Notes:
|
||||
# `COMPREPLY`: what will be rendered after completion is triggered
|
||||
# `completing_word`: currently typed word to generate completions for
|
||||
# `${!var}`: evaluates the content of `var` and expand its content as a variable
|
||||
# hello="world"
|
||||
# x="hello"
|
||||
# ${!x} -> ${hello} -> "world"
|
||||
_shtab_ahriman() {
|
||||
local completing_word="${COMP_WORDS[COMP_CWORD]}"
|
||||
COMPREPLY=()
|
||||
|
||||
prefix=_shtab_ahriman
|
||||
word_index=0
|
||||
_set_parser_defaults
|
||||
word_index=1
|
||||
|
||||
# determine what arguments are appropriate for the current state
|
||||
# of the arg parser
|
||||
while [ $word_index -ne $COMP_CWORD ]; do
|
||||
local this_word="${COMP_WORDS[$word_index]}"
|
||||
|
||||
if [[ -n $sub_parsers && " ${sub_parsers[@]} " =~ " ${this_word} " ]]; then
|
||||
# valid subcommand: add it to the prefix & reset the current action
|
||||
prefix="${prefix}_$(_shtab_replace_nonword $this_word)"
|
||||
_set_parser_defaults
|
||||
fi
|
||||
|
||||
if [[ " ${current_option_strings[@]} " =~ " ${this_word} " ]]; then
|
||||
# a new action should be acquired (due to recognised option string or
|
||||
# no more input expected from current action);
|
||||
# the next positional action can fill in here
|
||||
_set_new_action $this_word false
|
||||
fi
|
||||
|
||||
if [[ "$current_action_nargs" != "*" ]] && \
|
||||
[[ "$current_action_nargs" != "+" ]] && \
|
||||
[[ "$current_action_nargs" != *"..." ]] && \
|
||||
(( $word_index + 1 - $current_action_args_start_index >= \
|
||||
$current_action_nargs )); then
|
||||
$current_action_is_positional && let "completed_positional_actions += 1"
|
||||
_set_new_action "pos_${completed_positional_actions}" true
|
||||
fi
|
||||
|
||||
let "word_index+=1"
|
||||
done
|
||||
|
||||
# Generate the completions
|
||||
|
||||
if [[ "${completing_word}" == -* ]]; then
|
||||
# optional argument started: use option strings
|
||||
COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") )
|
||||
else
|
||||
# use choices & compgen
|
||||
local IFS=$'\n' # items may contain spaces, so delimit using newline
|
||||
COMPREPLY=( $([ -n "${current_action_compgen}" ] \
|
||||
&& "${current_action_compgen}" "${completing_word}") )
|
||||
unset IFS
|
||||
COMPREPLY+=( $(compgen -W "${current_action_choices[*]}" -- "${completing_word}") )
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -o filenames -F _shtab_ahriman ahriman
|
531
docs/completions/zsh/_ahriman
Normal file
531
docs/completions/zsh/_ahriman
Normal file
@ -0,0 +1,531 @@
|
||||
#compdef ahriman
|
||||
|
||||
# AUTOMATICALLY GENERATED by `shtab`
|
||||
|
||||
|
||||
_shtab_ahriman_commands() {
|
||||
local _commands=(
|
||||
"add:add existing or new package to the build queue"
|
||||
"aur-search:search for package in AUR using API"
|
||||
"check:check for packages updates. Same as repo-update --dry-run --no-manual"
|
||||
"clean:remove local caches"
|
||||
"config:dump configuration for the specified architecture"
|
||||
"daemon:start process which periodically will run update process"
|
||||
"help:show help message for application or command and exit"
|
||||
"help-commands-unsafe:list unsafe commands as defined in default args"
|
||||
"init:create initial service configuration, requires root"
|
||||
"key-import:import PGP key from public sources to the repository user"
|
||||
"package-add:add existing or new package to the build queue"
|
||||
"package-remove:remove package from the repository"
|
||||
"package-status:request status of the package"
|
||||
"package-status-remove:remove the package from the status page"
|
||||
"package-status-update:update package status on the status page"
|
||||
"package-update:add existing or new package to the build queue"
|
||||
"patch-add:create or update patched PKGBUILD function or variable"
|
||||
"patch-list:list available patches for the package"
|
||||
"patch-remove:remove patches for the package"
|
||||
"patch-set-add:create or update source patches"
|
||||
"rebuild:force rebuild whole repository"
|
||||
"remove:remove package from the repository"
|
||||
"remove-unknown:remove packages which are missing in AUR and do not have local PKGBUILDs"
|
||||
"repo-backup:backup repository settings and database"
|
||||
"repo-check:check for packages updates. Same as repo-update --dry-run --no-manual"
|
||||
"repo-clean:remove local caches"
|
||||
"repo-config:dump configuration for the specified architecture"
|
||||
"repo-init:create initial service configuration, requires root"
|
||||
"repo-rebuild:force rebuild whole repository"
|
||||
"repo-remove-unknown:remove packages which are missing in AUR and do not have local PKGBUILDs"
|
||||
"repo-report:generate repository report according to current settings"
|
||||
"repo-restore:restore settings and database"
|
||||
"repo-setup:create initial service configuration, requires root"
|
||||
"repo-sign:(re-)sign packages and repository database according to current settings"
|
||||
"repo-status-update:update repository status on the status page"
|
||||
"repo-sync:sync repository files to remote server according to current settings"
|
||||
"repo-tree:dump repository tree based on packages dependencies"
|
||||
"repo-triggers:run triggers on empty build result as configured by settings"
|
||||
"repo-update:check for packages updates and run build process if requested"
|
||||
"report:generate repository report according to current settings"
|
||||
"search:search for package in AUR using API"
|
||||
"setup:create initial service configuration, requires root"
|
||||
"shell:drop into python shell while having created application"
|
||||
"sign:(re-)sign packages and repository database according to current settings"
|
||||
"status:request status of the package"
|
||||
"status-update:update package status on the status page"
|
||||
"sync:sync repository files to remote server according to current settings"
|
||||
"update:check for packages updates and run build process if requested"
|
||||
"user-add:update user for web services with the given password and role. In case if password was not entered it will be asked interactively"
|
||||
"user-list:list users from the user mapping and their roles"
|
||||
"user-remove:remove user from the user mapping and update the configuration"
|
||||
"version:print application and its dependencies versions"
|
||||
"web:start web server"
|
||||
)
|
||||
_describe 'ahriman commands' _commands
|
||||
}
|
||||
|
||||
_shtab_ahriman_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*"{-a,--architecture}"[target architectures. For several subcommands it can be used multiple times]:architecture:"
|
||||
{-c,--configuration}"[configuration path]:configuration:"
|
||||
"--force[force run, remove file lock]"
|
||||
{-l,--lock}"[lock file]:lock:"
|
||||
{--report,--no-report}"[force enable or disable reporting to web service (default\: \%(default)s)]:report:"
|
||||
{-q,--quiet}"[force disable any logging]"
|
||||
"--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable]"
|
||||
"(- : *)"{-V,--version}"[show program\'s version number and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{-n,--now}"[run update function after]"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
|
||||
"--without-dependencies[do not add dependencies]"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_aur_search_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:"
|
||||
"--sort-by[sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted by name]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository url url_path version)"
|
||||
"(*):search terms, can be specified multiple times, the result will match all terms:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_check_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--vcs,--no-vcs}"[enable or disable checking of VCS packages (default\: \%(default)s)]:vcs:"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
"(*)::filter check by package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_clean_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--cache,--no-cache}"[clear directory with package caches (default\: \%(default)s)]:cache:"
|
||||
{--chroot,--no-chroot}"[clear build chroot (default\: \%(default)s)]:chroot:"
|
||||
{--manual,--no-manual}"[clear manually added packages queue (default\: \%(default)s)]:manual:"
|
||||
{--packages,--no-packages}"[clear directory with built packages (default\: \%(default)s)]:packages:"
|
||||
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: \%(default)s)]:pacman:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_daemon_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-i,--interval}"[interval between runs in seconds]:interval:"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates. Implies --no-vcs (default\: \%(default)s)]:aur:"
|
||||
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:"
|
||||
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:"
|
||||
{--vcs,--no-vcs}"[enable or disable checking of VCS packages (default\: \%(default)s)]:vcs:"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_help_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":show help message for specific command:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_help_commands_unsafe_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--command[instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise]:command:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_init_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one]:build_as_user:"
|
||||
"--build-command[build command prefix]:build_command:"
|
||||
"--from-configuration[path to default devtools pacman configuration]:from_configuration:"
|
||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:"
|
||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:"
|
||||
"--packager[packager name and email]:packager:"
|
||||
"--repository[repository name]:repository:"
|
||||
"--sign-key[sign key id]:sign_key:"
|
||||
"*--sign-target[sign options]:sign_target:(disabled packages repository)"
|
||||
"--web-port[port of the web service]:web_port:"
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications]:web_unix_socket:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_key_import_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--key-server[key server for key import]:key_server:"
|
||||
":PGP key to import from public server:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_package_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{-n,--now}"[run update function after]"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
|
||||
"--without-dependencies[do not add dependencies]"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_package_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):package name or base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_package_status_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--ahriman[get service status itself]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:"
|
||||
{-s,--status}"[filter packages by status]:status:(unknown pending building failed success)"
|
||||
"(*)::filter status by package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_package_status_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):remove specified packages from status page:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_package_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new package build status]:status:(unknown pending building failed success)"
|
||||
"(*)::set status for specified packages. If no packages supplied, service status will be updated:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_package_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{-n,--now}"[run update function after]"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
|
||||
"--without-dependencies[do not add dependencies]"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_patch_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":package base:"
|
||||
":PKGBUILD variable or function name. If variable is a function, it must end with ():"
|
||||
":path to file which contains function or variable value. If not set, the value will be read from stdin:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_patch_list_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
"*"{-v,--variable}"[if set, show only patches for specified PKGBUILD variables]:variable:"
|
||||
":package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_patch_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*"{-v,--variable}"[should be used for single-function patches in case if you wold like to remove only specified PKGBUILD variables. In case if not set, it will remove all patches related to the package]:variable:"
|
||||
":package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_patch_set_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*"{-t,--track}"[files which has to be tracked]:track:"
|
||||
":path to directory with changed files for patch addition\/update:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_rebuild_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*--depends-on[only rebuild packages that depend on specified packages]:depends_on:"
|
||||
"--dry-run[just perform check for packages without rebuild process itself]"
|
||||
"--from-database[read packages from database instead of filesystem. This feature in particular is required in case if you would like to restore repository from another repository instance. Note, however, that in order to restore packages you need to have original ahriman instance run with web service and have run repo-update at least once.]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):package name or base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_remove_unknown_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--dry-run[just perform check for packages without removal]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_backup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":path of the output archive:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_check_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--vcs,--no-vcs}"[enable or disable checking of VCS packages (default\: \%(default)s)]:vcs:"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
"(*)::filter check by package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_clean_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--cache,--no-cache}"[clear directory with package caches (default\: \%(default)s)]:cache:"
|
||||
{--chroot,--no-chroot}"[clear build chroot (default\: \%(default)s)]:chroot:"
|
||||
{--manual,--no-manual}"[clear manually added packages queue (default\: \%(default)s)]:manual:"
|
||||
{--packages,--no-packages}"[clear directory with built packages (default\: \%(default)s)]:packages:"
|
||||
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: \%(default)s)]:pacman:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_init_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one]:build_as_user:"
|
||||
"--build-command[build command prefix]:build_command:"
|
||||
"--from-configuration[path to default devtools pacman configuration]:from_configuration:"
|
||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:"
|
||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:"
|
||||
"--packager[packager name and email]:packager:"
|
||||
"--repository[repository name]:repository:"
|
||||
"--sign-key[sign key id]:sign_key:"
|
||||
"*--sign-target[sign options]:sign_target:(disabled packages repository)"
|
||||
"--web-port[port of the web service]:web_port:"
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications]:web_unix_socket:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_rebuild_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*--depends-on[only rebuild packages that depend on specified packages]:depends_on:"
|
||||
"--dry-run[just perform check for packages without rebuild process itself]"
|
||||
"--from-database[read packages from database instead of filesystem. This feature in particular is required in case if you would like to restore repository from another repository instance. Note, however, that in order to restore packages you need to have original ahriman instance run with web service and have run repo-update at least once.]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_remove_unknown_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--dry-run[just perform check for packages without removal]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_report_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_restore_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-o,--output}"[root path of the extracted files]:output:"
|
||||
":path of the input archive:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_setup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one]:build_as_user:"
|
||||
"--build-command[build command prefix]:build_command:"
|
||||
"--from-configuration[path to default devtools pacman configuration]:from_configuration:"
|
||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:"
|
||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:"
|
||||
"--packager[packager name and email]:packager:"
|
||||
"--repository[repository name]:repository:"
|
||||
"--sign-key[sign key id]:sign_key:"
|
||||
"*--sign-target[sign options]:sign_target:(disabled packages repository)"
|
||||
"--web-port[port of the web service]:web_port:"
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications]:web_unix_socket:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_sign_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*)::sign only specified packages:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new status]:status:(unknown pending building failed success)"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_sync_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_tree_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_triggers_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*)::instead of running all triggers as set by configuration, just process specified ones in order of mention:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--dry-run[just perform check for updates, same as check command]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates. Implies --no-vcs (default\: \%(default)s)]:aur:"
|
||||
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:"
|
||||
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:"
|
||||
{--vcs,--no-vcs}"[enable or disable checking of VCS packages (default\: \%(default)s)]:vcs:"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
"(*)::filter check by package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_report_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_search_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:"
|
||||
"--sort-by[sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted by name]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository url url_path version)"
|
||||
"(*):search terms, can be specified multiple times, the result will match all terms:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_setup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one]:build_as_user:"
|
||||
"--build-command[build command prefix]:build_command:"
|
||||
"--from-configuration[path to default devtools pacman configuration]:from_configuration:"
|
||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: \%(default)s)]:makeflags_jobs:"
|
||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: \%(default)s)]:multilib:"
|
||||
"--packager[packager name and email]:packager:"
|
||||
"--repository[repository name]:repository:"
|
||||
"--sign-key[sign key id]:sign_key:"
|
||||
"*--sign-target[sign options]:sign_target:(disabled packages repository)"
|
||||
"--web-port[port of the web service]:web_port:"
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications]:web_unix_socket:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_shell_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":instead of dropping into shell, just execute the specified code:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_sign_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*)::sign only specified packages:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_status_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--ahriman[get service status itself]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:"
|
||||
{-s,--status}"[filter packages by status]:status:(unknown pending building failed success)"
|
||||
"(*)::filter status by package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new package build status]:status:(unknown pending building failed success)"
|
||||
"(*)::set status for specified packages. If no packages supplied, service status will be updated:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_sync_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--dry-run[just perform check for updates, same as check command]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates. Implies --no-vcs (default\: \%(default)s)]:aur:"
|
||||
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:"
|
||||
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:"
|
||||
{--vcs,--no-vcs}"[enable or disable checking of VCS packages (default\: \%(default)s)]:vcs:"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
|
||||
"(*)::filter check by package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_user_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-p,--password}"[user password. Blank password will be treated as empty password, which is in particular must be used for OAuth2 authorization type.]:password:"
|
||||
{-r,--role}"[user access level]:role:(unauthorized read reporter full)"
|
||||
{-s,--secure}"[set file permissions to user-only]"
|
||||
":username for web service:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_user_list_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty]"
|
||||
{-r,--role}"[filter users by role]:role:(unauthorized read reporter full)"
|
||||
":filter users by username:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_user_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":username for web service:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_version_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_web_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
|
||||
_shtab_ahriman() {
|
||||
local context state line curcontext="$curcontext" one_or_more='(-)*' remainder='(*)'
|
||||
|
||||
if ((${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501
|
||||
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman')
|
||||
fi
|
||||
_arguments -C $_shtab_ahriman_options
|
||||
|
||||
case $state in
|
||||
ahriman)
|
||||
words=($line[1] "${words[@]}")
|
||||
(( CURRENT += 1 ))
|
||||
curcontext="${curcontext%:*:*}:_shtab_ahriman-$line[1]:"
|
||||
case $line[1] in
|
||||
add) _arguments -C $_shtab_ahriman_add_options ;;
|
||||
aur-search) _arguments -C $_shtab_ahriman_aur_search_options ;;
|
||||
check) _arguments -C $_shtab_ahriman_check_options ;;
|
||||
clean) _arguments -C $_shtab_ahriman_clean_options ;;
|
||||
config) _arguments -C $_shtab_ahriman_config_options ;;
|
||||
daemon) _arguments -C $_shtab_ahriman_daemon_options ;;
|
||||
help) _arguments -C $_shtab_ahriman_help_options ;;
|
||||
help-commands-unsafe) _arguments -C $_shtab_ahriman_help_commands_unsafe_options ;;
|
||||
init) _arguments -C $_shtab_ahriman_init_options ;;
|
||||
key-import) _arguments -C $_shtab_ahriman_key_import_options ;;
|
||||
package-add) _arguments -C $_shtab_ahriman_package_add_options ;;
|
||||
package-remove) _arguments -C $_shtab_ahriman_package_remove_options ;;
|
||||
package-status) _arguments -C $_shtab_ahriman_package_status_options ;;
|
||||
package-status-remove) _arguments -C $_shtab_ahriman_package_status_remove_options ;;
|
||||
package-status-update) _arguments -C $_shtab_ahriman_package_status_update_options ;;
|
||||
package-update) _arguments -C $_shtab_ahriman_package_update_options ;;
|
||||
patch-add) _arguments -C $_shtab_ahriman_patch_add_options ;;
|
||||
patch-list) _arguments -C $_shtab_ahriman_patch_list_options ;;
|
||||
patch-remove) _arguments -C $_shtab_ahriman_patch_remove_options ;;
|
||||
patch-set-add) _arguments -C $_shtab_ahriman_patch_set_add_options ;;
|
||||
rebuild) _arguments -C $_shtab_ahriman_rebuild_options ;;
|
||||
remove) _arguments -C $_shtab_ahriman_remove_options ;;
|
||||
remove-unknown) _arguments -C $_shtab_ahriman_remove_unknown_options ;;
|
||||
repo-backup) _arguments -C $_shtab_ahriman_repo_backup_options ;;
|
||||
repo-check) _arguments -C $_shtab_ahriman_repo_check_options ;;
|
||||
repo-clean) _arguments -C $_shtab_ahriman_repo_clean_options ;;
|
||||
repo-config) _arguments -C $_shtab_ahriman_repo_config_options ;;
|
||||
repo-init) _arguments -C $_shtab_ahriman_repo_init_options ;;
|
||||
repo-rebuild) _arguments -C $_shtab_ahriman_repo_rebuild_options ;;
|
||||
repo-remove-unknown) _arguments -C $_shtab_ahriman_repo_remove_unknown_options ;;
|
||||
repo-report) _arguments -C $_shtab_ahriman_repo_report_options ;;
|
||||
repo-restore) _arguments -C $_shtab_ahriman_repo_restore_options ;;
|
||||
repo-setup) _arguments -C $_shtab_ahriman_repo_setup_options ;;
|
||||
repo-sign) _arguments -C $_shtab_ahriman_repo_sign_options ;;
|
||||
repo-status-update) _arguments -C $_shtab_ahriman_repo_status_update_options ;;
|
||||
repo-sync) _arguments -C $_shtab_ahriman_repo_sync_options ;;
|
||||
repo-tree) _arguments -C $_shtab_ahriman_repo_tree_options ;;
|
||||
repo-triggers) _arguments -C $_shtab_ahriman_repo_triggers_options ;;
|
||||
repo-update) _arguments -C $_shtab_ahriman_repo_update_options ;;
|
||||
report) _arguments -C $_shtab_ahriman_report_options ;;
|
||||
search) _arguments -C $_shtab_ahriman_search_options ;;
|
||||
setup) _arguments -C $_shtab_ahriman_setup_options ;;
|
||||
shell) _arguments -C $_shtab_ahriman_shell_options ;;
|
||||
sign) _arguments -C $_shtab_ahriman_sign_options ;;
|
||||
status) _arguments -C $_shtab_ahriman_status_options ;;
|
||||
status-update) _arguments -C $_shtab_ahriman_status_update_options ;;
|
||||
sync) _arguments -C $_shtab_ahriman_sync_options ;;
|
||||
update) _arguments -C $_shtab_ahriman_update_options ;;
|
||||
user-add) _arguments -C $_shtab_ahriman_user_add_options ;;
|
||||
user-list) _arguments -C $_shtab_ahriman_user_list_options ;;
|
||||
user-remove) _arguments -C $_shtab_ahriman_user_remove_options ;;
|
||||
version) _arguments -C $_shtab_ahriman_version_options ;;
|
||||
web) _arguments -C $_shtab_ahriman_web_options ;;
|
||||
esac
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
|
||||
typeset -A opt_args
|
||||
_shtab_ahriman "$@"
|
16
docs/conf.py
16
docs/conf.py
@ -25,20 +25,10 @@ sys.path.insert(0, str(basedir))
|
||||
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
|
||||
|
||||
for module in (
|
||||
"aioauth_client",
|
||||
"aiohttp",
|
||||
"aiohttp.web",
|
||||
"aiohttp.web_exceptions",
|
||||
"aiohttp.web_response",
|
||||
"aiohttp.web_urldispatcher",
|
||||
"aiohttp_jinja2",
|
||||
"aiohttp_security",
|
||||
"aiohttp_session",
|
||||
"aiohttp_session.cookie_storage",
|
||||
"boto3",
|
||||
"cryptography",
|
||||
"pyalpm",
|
||||
):
|
||||
if module in sys.modules:
|
||||
continue
|
||||
sys.modules[module] = mock.Mock()
|
||||
|
||||
|
||||
@ -89,7 +79,7 @@ html_theme = "default" if on_rtd else "alabaster"
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
html_static_path = []
|
||||
|
||||
add_module_names = False
|
||||
|
||||
|
@ -44,7 +44,7 @@ Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` libr
|
||||
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
|
||||
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
|
||||
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
|
||||
* ``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 ``user-add`` subcommand).
|
||||
|
||||
Authorized users are stored inside internal database, if any of external provides are used the password field for non-service users must be empty.
|
||||
|
||||
@ -59,6 +59,7 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
|
||||
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
|
||||
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
|
||||
* ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of mention.
|
||||
* ``vcs_allowed_age`` - maximal age in seconds of the VCS packages before their version will be updated with its remote source, int, optional, default ``0``.
|
||||
|
||||
``repository`` group
|
||||
--------------------
|
||||
@ -104,6 +105,7 @@ It supports authorization; to do so you'd need to prefix the url with authorizat
|
||||
Remote push trigger
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* ``commit_author`` - git commit author, string, optional. In case if not set, the git will generate author for you. Note, however, that in this case it will disclosure your hostname.
|
||||
* ``push_url`` - url of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
|
||||
* ``push_branch`` - branch of the remote repository to which PKGBUILDs should be pushed after build process, string, optional, default is ``master``.
|
||||
|
||||
@ -114,7 +116,7 @@ Report generation settings.
|
||||
|
||||
* ``target`` - list of reports to be generated, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. ``somerandomname`` must point to existing section, ``email`` must point to either ``email`` or ``email:x86_64`` (the one with architecture has higher priority).
|
||||
|
||||
Type will be read from several ways:
|
||||
Type will be read from several sources:
|
||||
|
||||
* In case if ``type`` option set inside the section, it will be used.
|
||||
* Otherwise, it will look for type from section name removing architecture name.
|
||||
@ -179,7 +181,7 @@ Remote synchronization settings.
|
||||
|
||||
* ``target`` - list of synchronizations to be used, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. ``somerandomname`` must point to existing section, ``github`` must point to one of ``github`` of ``github:x86_64`` (with architecture it has higher priority).
|
||||
|
||||
Type will be read from several ways:
|
||||
Type will be read from several sources:
|
||||
|
||||
* In case if ``type`` option set inside the section, it will be used.
|
||||
* Otherwise, it will look for type from section name removing architecture name.
|
||||
@ -239,4 +241,5 @@ Web server settings. If any of ``host``/``port`` is not set, web integration wil
|
||||
* ``port`` - port to bind, int, optional.
|
||||
* ``static_path`` - path to directory with static files, string, required.
|
||||
* ``templates`` - path to templates directory, string, required.
|
||||
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
||||
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||
|
169
docs/faq.rst
169
docs/faq.rst
@ -169,13 +169,30 @@ Unlike ``RemotePullTrigger`` trigger, the ``RemotePushTrigger`` more likely will
|
||||
How to change PKGBUILDs before build
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Well it is supported also. The recommended way is to patch specific function, e.g. by running ``sudo -u ahriman ahriman patch-add ahriman version``. This command will prompt for new value of the PKGBUILD variable ``version``. You can also write it to file and read from it ``sudo -u ahriman ahriman patch-add ahriman version version.patch``.
|
||||
Well it is supported also. The recommended way is to patch specific function, e.g. by running
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman patch-add ahriman version
|
||||
|
||||
This command will prompt for new value of the PKGBUILD variable ``version``. You can also write it to file and read from it:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman patch-add ahriman version version.patch
|
||||
|
||||
Alternatively you can create full-diff patches, which are calculated by using ``git diff`` from current PKGBUILD master branch:
|
||||
|
||||
#. Clone sources from AUR.
|
||||
#. Make changes you would like to (e.g. edit ``PKGBUILD``, add external patches).
|
||||
#. Run ``sudo -u ahriman ahriman patch-set-add /path/to/local/directory/with/PKGBUILD``.
|
||||
#.
|
||||
Clone sources from AUR.
|
||||
#.
|
||||
Make changes you would like to (e.g. edit ``PKGBUILD``, add external patches).
|
||||
#.
|
||||
Run command
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman patch-set-add /path/to/local/directory/with/PKGBUILD
|
||||
|
||||
The last command will calculate diff from current tree to the ``HEAD`` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
|
||||
|
||||
@ -219,6 +236,62 @@ Also, there is command ``repo-remove-unknown`` which checks packages in AUR and
|
||||
|
||||
Remove commands also remove any package files (patches, caches etc).
|
||||
|
||||
How to sign repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Repository sign feature is available in several configurations. The recommended way is just to sign repository database file by single key instead of trying to sign each package. However, the steps are pretty same, just configuration is a bit differ. For more details about options kindly refer to :doc:`configuration reference <configuration>`.
|
||||
|
||||
#.
|
||||
First you would need to create the key on your local machine:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
gpg --full-generate-key
|
||||
|
||||
This command will prompt you for several questions. Most of them may be left default, but you will need to fill real name and email address with some data. Because at the moment the service doesn't support passphrases, it must be left blank.
|
||||
|
||||
#.
|
||||
The command above will generate key and print its hash, something like ``8BE91E5A773FB48AC05CC1EDBED105AED6246B39``. Copy it.
|
||||
|
||||
#.
|
||||
Export your private key by using the hash above:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
gpg --export-secret-keys -a 8BE91E5A773FB48AC05CC1EDBED105AED6246B39 > repository-key.gpg
|
||||
|
||||
#.
|
||||
|
||||
Copy the specified key to the build machine (i.e. where the service is running).
|
||||
|
||||
#.
|
||||
Import the specified key to the service user:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman gpg --import repository-key.gpg
|
||||
|
||||
Don't forget to remove the key from filesystem after import.
|
||||
|
||||
#.
|
||||
Change trust level to ``ultimate``:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman gpg --edit-key 8BE91E5A773FB48AC05CC1EDBED105AED6246B39
|
||||
|
||||
The command above will drop you into gpg shell, in which you will need to type ``trust``, choose ``5 = I trust ultimately``, confirm and exit ``quit``.
|
||||
|
||||
#.
|
||||
Proceed with service configuration according to the :doc:`configuration <configuration>`:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[sign]
|
||||
target = repository
|
||||
key = 8BE91E5A773FB48AC05CC1EDBED105AED6246B39
|
||||
|
||||
|
||||
How to rebuild packages after library update
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -290,17 +363,17 @@ The default action (in case if no arguments provided) is ``repo-update``. Basica
|
||||
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
``--privileged`` flag is required to make mount possible inside container. In addition, you can pass own configuration overrides by using the same ``-v`` flag, e.g.:
|
||||
``--privileged`` flag is required to make mount possible inside container. In order to make data available outside of container, you would need to mount local (parent) directory inside container by using ``-v /path/to/local/repo:/var/lib/ahriman`` argument, where ``/path/to/local/repo`` is a path to repository on local machine. In addition, you can pass own configuration overrides by using the same ``-v`` flag, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman -v /path/to/overrides/overrides.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
|
||||
|
||||
The action can be specified during run, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run arcan1s/ahriman:latest package-add ahriman --now
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest package-add ahriman --now
|
||||
|
||||
For more details please refer to docker FAQ.
|
||||
|
||||
@ -318,13 +391,25 @@ The following environment variables are supported:
|
||||
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
|
||||
* ``AHRIMAN_REPOSITORY`` - repository name, default is ``aur-clone``.
|
||||
* ``AHRIMAN_REPOSITORY_ROOT`` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses ``ahriman`` directory inside ahriman's home, which can be passed as mount volume.
|
||||
* ``AHRIMAN_UNIX_SOCKET`` - full path to unix socket which is used by web server, default is empty. Note that more likely you would like to put it inside ``AHRIMAN_REPOSITORY_ROOT`` directory (e.g. ``/var/lib/ahriman/ahriman/ahriman-web.sock``) or to ``/tmp``.
|
||||
* ``AHRIMAN_USER`` - ahriman user, usually must not be overwritten, default is ``ahriman``.
|
||||
|
||||
You can pass any of these variables by using ``-e`` argument, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run -e AHRIMAN_PORT=8080 arcan1s/ahriman:latest
|
||||
docker run --privileged -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Daemon service
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
There is special ``daemon`` subcommand which emulates systemd timer and will perform repository update periodically:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest daemon
|
||||
|
||||
This command uses same rules as ``repo-update``, thus, e.g. requires ``--privileged`` flag.
|
||||
|
||||
Web service setup
|
||||
^^^^^^^^^^^^^^^^^
|
||||
@ -333,26 +418,23 @@ Well for that you would need to have web container instance running forever; it
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run -p 8080:8080 -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Note about ``AHRIMAN_PORT`` environment variable which is required in order to enable web service. An additional port bind by ``-p 8080:8080`` is required to pass docker port outside of container.
|
||||
|
||||
For every next container run use arguments ``-e AHRIMAN_PORT=8080 --net=host``, e.g.:
|
||||
The ``AHRIMAN_UNIX_SOCKET`` variable is not required, however, highly recommended as it can be used for interprocess communications. If you set this variable you would like to be sure that this path is available outside of container if you are going to use multiple docker instances.
|
||||
|
||||
If you are using ``AHRIMAN_UNIX_SOCKET`` variable, for every next container run it has to be passed also, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -e AHRIMAN_PORT=8080 --net=host -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
docker run --privileged -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Daemon service
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
There is special subcommand which emulates systemd timer and will perform repository update periodically:
|
||||
Otherwise, you would need to pass ``AHRIMAN_PORT`` and mount container network to the host system (``--net=host``), e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run arcan1s/ahriman:latest daemon
|
||||
|
||||
This command uses same rules as ``repo-update``, thus, e.g. requires ``--privileged`` flag.
|
||||
docker run --privileged --net=host -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Remote synchronization
|
||||
----------------------
|
||||
@ -610,19 +692,41 @@ How to enable basic authorization
|
||||
[auth]
|
||||
target = configuration
|
||||
|
||||
#.
|
||||
Create user for the service:
|
||||
#.
|
||||
In order to provide access for reporting from application instances you can (recommended way) use unix sockets by configuring the following (note, that it requires ``python-requests-unixsocket`` package to be installed):
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[web]
|
||||
unix_socket = /var/lib/ahriman/ahriman-web.sock
|
||||
|
||||
This socket path must be available for web service instance and must be available for application instances (e.g. in case if you are using docker container, see above, you need to be sure that the socket is passed to the root filesystem).
|
||||
|
||||
By the way, unix socket variable will be automatically set in case if ``--web-unix-socket`` argument is supplied to the ``setup`` subcommand.
|
||||
|
||||
Alternatively, you need to create user for the service:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add --as-service -r write api
|
||||
sudo -u ahriman ahriman user-add -r full api
|
||||
|
||||
This command will ask for the password, just type it in stdin; *do not* leave the field blank, user will not be able to authorize.
|
||||
This command will ask for the password, just type it in stdin; *do not* leave the field blank, user will not be able to authorize, and finally configure the application:
|
||||
|
||||
#.
|
||||
Create end-user ``sudo -u ahriman ahriman user-add -r write my-first-user`` with password.
|
||||
.. code-block:: ini
|
||||
|
||||
#. Restart web service ``systemctl restart ahriman-web@x86_64``.
|
||||
[web]
|
||||
username = api
|
||||
password = pa55w0rd
|
||||
|
||||
#.
|
||||
Create end-user with password:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add -r full my-first-user
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web@x86_64``.
|
||||
|
||||
How to enable OAuth authorization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -656,12 +760,19 @@ How to enable OAuth authorization
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add --as-service -r write api
|
||||
sudo -u ahriman ahriman user-add --as-service -r full api
|
||||
|
||||
#.
|
||||
Create end-user ``sudo -u ahriman ahriman user-add -r write my-first-user``. When it will ask for the password leave it blank.
|
||||
Create end-user:
|
||||
|
||||
#. Restart web service ``systemctl restart ahriman-web@x86_64``.
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add -r full my-first-user
|
||||
|
||||
When it will ask for the password leave it blank.
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web@x86_64``.
|
||||
|
||||
Backup and restore
|
||||
------------------
|
||||
@ -681,7 +792,7 @@ The service provides several commands aim to do easy repository backup and resto
|
||||
Copy created archive from source server ``server1.example.com`` to target ``server2.example.com``.
|
||||
|
||||
#.
|
||||
Install ahriman as usual on the target server ``server2.example.com`` if you didn't yet.
|
||||
Install package as usual on the target server ``server2.example.com`` if you didn't yet.
|
||||
|
||||
#.
|
||||
Extract archive e.g. by using subcommand:
|
||||
|
@ -16,6 +16,13 @@ Features
|
||||
* Triggers for repository updates, e.g. synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram).
|
||||
* Repository status interface with optional authorization and control options.
|
||||
|
||||
Live demos
|
||||
----------
|
||||
|
||||
* `Build status page <https://ahriman-demo.arcanis.me>`_. You can login as ``demo`` user by using ``demo`` password. Note, however, you will not be able to run tasks.
|
||||
* `Repository index <http://repo.arcanis.me/x86_64/index.html>`_.
|
||||
* `Telegram feed <https://t.me/arcanisrepo>`_.
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
|
@ -19,7 +19,7 @@ For the configuration details and settings explanation kindly refer to the :doc:
|
||||
|
||||
This trigger will be called before any action (``on_start``) and pulls remote PKGBUILD repository locally; after that it copies found PKGBUILDs from the cloned repository to the local cache. It is useful in case if you have patched PGKBUILDs (or even missing in AUR) which you would like to use for package building and, technically, just simplifies the local package building.
|
||||
|
||||
In order to update those packages you would need to clone your repository separately, make changes in PKGBUILD (e.g. bump version and update checksums), commit them and push back. On the next ahriman's repository update, it will pull changes you commited and will perform package update.
|
||||
In order to update those packages you would need to clone your repository separately, make changes in PKGBUILD (e.g. bump version and update checksums), commit them and push back. On the next ahriman's repository update, it will pull changes you committed and will perform package update.
|
||||
|
||||
``ahriman.core.gitremote.RemotePushTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -36,6 +36,40 @@ Trigger which can be used for reporting. It implements ``on_result`` method and
|
||||
|
||||
This trigger takes build result (``on_result``) and performs syncing of the local packages to the remote mirror (e.g. S3 or just by rsync).
|
||||
|
||||
Context variables
|
||||
-----------------
|
||||
|
||||
By default, only configuration and architecture are passed to triggers. However, some triggers might want to have access to other high-level wrappers. In order to provide such ability and avoid (double) initialization, the service provides a global context variables, which can be accessed from ``ahriman.core`` package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ahriman.core import context
|
||||
|
||||
ctx = context.get()
|
||||
|
||||
Just because context is wrapped inside ``contexvars.ContextVar``, you need to explicitly extract variable by ``get()`` method. Later you can extract any variable if it is set, e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.models.context_key import ContextKey
|
||||
|
||||
database = ctx.get(ContextKey("database", SQLite))
|
||||
|
||||
In order to provide typed API, all variables are stored together with their type. The ``get(ContextKey)`` method will throw ``KeyError`` in case if key is missing. Alternatively you can set your own variable inside context:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ctx.set(ContextKey("answer", int), 42)
|
||||
context.set(ctx)
|
||||
|
||||
Note, however, that there are several limitations:
|
||||
|
||||
* Context variables are immutable, thus you cannot override value if the key already presented.
|
||||
* The ``return_type`` of ``ContextKey`` should match the value type, otherwise exception will be thrown.
|
||||
|
||||
The ``context`` also implements collection methods such as ``__iter__`` and ``__len__``.
|
||||
|
||||
Trigger example
|
||||
---------------
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Maintainer: Evgeniy Alekseev
|
||||
|
||||
pkgname='ahriman'
|
||||
pkgver=2.3.0rc2
|
||||
pkgver=2.5.4
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH linux ReposItory MANager"
|
||||
arch=('any')
|
||||
@ -20,6 +20,7 @@ optdepends=('breezy: -bzr packages support'
|
||||
'python-aiohttp-session: web server with authorization'
|
||||
'python-boto3: sync to s3'
|
||||
'python-cryptography: web server with authorization'
|
||||
'python-requests-unixsocket: client report to web server by unix socket'
|
||||
'python-jinja: html report generation'
|
||||
'rsync: sync by using rsync'
|
||||
'subversion: -svn packages support')
|
||||
|
@ -24,6 +24,7 @@ ignore_packages =
|
||||
makechrootpkg_flags =
|
||||
makepkg_flags = --nocolor --ignorearch
|
||||
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
|
||||
vcs_allowed_age = 604800
|
||||
|
||||
[repository]
|
||||
name = aur-clone
|
||||
|
@ -20,11 +20,11 @@ formatter = syslog_format
|
||||
args = ("/dev/log",)
|
||||
|
||||
[formatter_generic_format]
|
||||
format = [%(levelname)s %(asctime)s] [%(threadName)s] [%(name)s]: %(message)s
|
||||
format = [%(levelname)s %(asctime)s] [%(name)s]: %(message)s
|
||||
datefmt =
|
||||
|
||||
[formatter_syslog_format]
|
||||
format = [%(levelname)s] [%(threadName)s] [%(name)s]: %(message)s
|
||||
format = [%(levelname)s] [%(name)s]: %(message)s
|
||||
datefmt =
|
||||
|
||||
[logger_root]
|
||||
|
@ -12,35 +12,56 @@
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h1>ahriman
|
||||
<img id="badge-version" src="https://img.shields.io/badge/version-unknown-informational" alt="unknown">
|
||||
<img id="badge-repository" src="https://img.shields.io/badge/repository-unknown-informational" alt="unknown">
|
||||
<img id="badge-architecture" src="https://img.shields.io/badge/architecture-unknown-informational" alt="unknown">
|
||||
<img id="badge-status" src="https://img.shields.io/badge/service%20status-unknown-inactive" alt="unknown">
|
||||
</h1>
|
||||
</div>
|
||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||
|
||||
<div class="container">
|
||||
<div id="toolbar">
|
||||
<h1 id="badge-repository">ahriman</h1>
|
||||
</div>
|
||||
|
||||
<div id="alert-placeholder" class="toast-container p3 top-0 start-50 translate-middle-x"></div>
|
||||
|
||||
<div class="container">
|
||||
<div id="toolbar" class="dropdown">
|
||||
<a id="badge-status" tabindex="0" role="button" class="btn btn-outline-secondary" data-bs-toggle="popover" data-bs-trigger="focus" data-bs-content="no run data"><i class="bi bi-info-circle"></i></a>
|
||||
|
||||
{% if not auth.enabled or auth.username is not none %}
|
||||
<button id="add-btn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-form" hidden>
|
||||
<i class="bi bi-plus"></i> add
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-box"></i> packages
|
||||
</button>
|
||||
<button id="update-btn" class="btn btn-secondary" onclick="updatePackages()" hidden>
|
||||
<i class="bi bi-play"></i> update
|
||||
</button>
|
||||
<button id="remove-btn" class="btn btn-danger" onclick="removePackages()" disabled hidden>
|
||||
<i class="bi bi-trash"></i> remove
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button id="package-add-btn" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal" hidden>
|
||||
<i class="bi bi-plus"></i> add
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-update-btn" class="btn dropdown-item" onclick="updatePackages()" hidden>
|
||||
<i class="bi bi-play"></i> update
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-rebuild-btn" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal" hidden>
|
||||
<i class="bi bi-arrow-clockwise"></i> rebuild
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-remove-btn" class="btn dropdown-item" onclick="removePackages()" disabled hidden>
|
||||
<i class="bi bi-trash"></i> remove
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button id="key-import-btn" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
|
||||
<i class="bi bi-key"></i> import key
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="btn btn-secondary" onclick="reload()">
|
||||
|
||||
<button type="button" class="btn btn-secondary" onclick="reload()">
|
||||
<i class="bi bi-arrow-clockwise"></i> reload
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table id="packages" class="table table-striped table-hover"
|
||||
data-click-to-select="true"
|
||||
data-export-options='{"fileName": "packages"}'
|
||||
data-page-list="[10, 25, 50, 100, all]"
|
||||
data-page-size="10"
|
||||
@ -76,25 +97,29 @@
|
||||
<div class="container">
|
||||
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
|
||||
<ul class="nav">
|
||||
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources">ahriman</a></li>
|
||||
<li><a id="badge-version" class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li>
|
||||
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li>
|
||||
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
|
||||
</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>
|
||||
<li><a class="nav-link" href="{{ index_url }}" title="repo index"><i class="bi bi-house"></i> repo index</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if auth.enabled %}
|
||||
{% if auth.username is none %}
|
||||
{{ auth.control|safe }}
|
||||
{% else %}
|
||||
<form action="/api/v1/logout" method="post">
|
||||
<button class="btn btn-link" style="text-decoration: none">logout ({{ auth.username }})</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<ul class="nav">
|
||||
{% if auth.username is none %}
|
||||
<li>{{ auth.control|safe }}</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<form action="/api/v1/logout" method="post">
|
||||
<button class="btn btn-link" style="text-decoration: none"><i class="bi bi-box-arrow-right"></i> logout ({{ auth.username }})</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</footer>
|
||||
</div>
|
||||
@ -103,12 +128,13 @@
|
||||
{% include "build-status/login-modal.jinja2" %}
|
||||
{% endif %}
|
||||
|
||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||
|
||||
{% include "build-status/failed-modal.jinja2" %}
|
||||
{% include "build-status/success-modal.jinja2" %}
|
||||
{% include "build-status/alerts.jinja2" %}
|
||||
|
||||
{% include "build-status/package-add-modal.jinja2" %}
|
||||
{% include "build-status/package-rebuild-modal.jinja2" %}
|
||||
{% include "build-status/key-import-modal.jinja2" %}
|
||||
|
||||
{% include "build-status/package-info-modal.jinja2" %}
|
||||
|
||||
{% include "build-status/table.jinja2" %}
|
||||
|
||||
|
45
package/share/ahriman/templates/build-status/alerts.jinja2
Normal file
45
package/share/ahriman/templates/build-status/alerts.jinja2
Normal file
@ -0,0 +1,45 @@
|
||||
<script>
|
||||
const alertPlaceholder = $("#alert-placeholder");
|
||||
|
||||
function createAlert(title, message, clz) {
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.classList.add("toast", clz);
|
||||
wrapper.role = "alert";
|
||||
wrapper.ariaLive = "assertive";
|
||||
wrapper.ariaAtomic = "true";
|
||||
wrapper.style.width = "500px"; // 500px is default modal size
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.classList.add("toast-header");
|
||||
header.innerHTML = `<strong class="me-auto">${safe(title)}</strong> <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="close"></button>`;
|
||||
wrapper.appendChild(header);
|
||||
|
||||
const body = document.createElement("div");
|
||||
body.classList.add("toast-body", "text-bg-light");
|
||||
body.innerText = message;
|
||||
wrapper.appendChild(body);
|
||||
|
||||
alertPlaceholder.append(wrapper);
|
||||
const toast = new bootstrap.Toast(wrapper);
|
||||
wrapper.addEventListener("hidden.bs.toast", () => {
|
||||
wrapper.remove(); // bootstrap doesn't remove elements
|
||||
reload();
|
||||
});
|
||||
toast.show();
|
||||
}
|
||||
|
||||
function showFailure(title, description, jqXHR, errorThrown) {
|
||||
let details;
|
||||
try {
|
||||
details = $.parseJSON(jqXHR.responseText).error; // execution handler json error response
|
||||
} catch (_) {
|
||||
details = errorThrown;
|
||||
}
|
||||
createAlert(title, description(details), "text-bg-danger");
|
||||
}
|
||||
|
||||
function showSuccess(title, description) {
|
||||
createAlert(title, description, "text-bg-success");
|
||||
}
|
||||
|
||||
</script>
|
@ -1,28 +0,0 @@
|
||||
<div id="failed-form" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger">
|
||||
<h4 class="modal-title">failed</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Packages update has failed.</p>
|
||||
<p id="error-details"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const failedForm = $("#failed-form");
|
||||
const errorDetails = $("#error-details");
|
||||
failedForm.on("hidden.bs.modal", () => { reload(); });
|
||||
|
||||
function showFailure(details) {
|
||||
errorDetails.text(details);
|
||||
failedForm.modal("show");
|
||||
}
|
||||
</script>
|
@ -0,0 +1,93 @@
|
||||
<div id="key-import-modal" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="key-import-form" onsubmit="return false">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Import key from PGP server</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="key-fingerprint-input" class="col-sm-2 col-form-label">fingerprint</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="key-fingerprint-input" type="text" class="form-control" placeholder="PGP key fingerprint" name="key" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="key-server-input" class="col-sm-2 col-form-label">key server</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="key-server-input" type="text" class="form-control" placeholder="PGP key server" name="server" value="keyserver.ubuntu.com" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-2"></div>
|
||||
<div class="col-sm-10">
|
||||
<pre class="language-less"><samp id="key-body-input" class="pre-scrollable language-less"></samp><button id="key-copy-btn" type="button" class="btn language-less" onclick="copyPgpKey()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" onclick="importPgpKey()"><i class="bi bi-play"></i> import</button>
|
||||
<button type="submit" class="btn btn-success" onclick="fetchPgpKey()"><i class="bi bi-arrow-clockwise"></i> fetch</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const keyImportModal = $("#key-import-modal");
|
||||
const keyImportForm = $("#key-import-form");
|
||||
keyImportModal.on("hidden.bs.modal", () => {
|
||||
keyBodyInput.text("");
|
||||
keyImportForm.trigger("reset");
|
||||
});
|
||||
|
||||
const keyBodyInput = $("#key-body-input");
|
||||
const keyCopyButton = $("#key-copy-btn");
|
||||
|
||||
const keyFingerprintInput = $("#key-fingerprint-input");
|
||||
const keyServerInput = $("#key-server-input");
|
||||
|
||||
async function copyPgpKey() {
|
||||
const logs = keyBodyInput.text();
|
||||
await copyToClipboard(logs, keyCopyButton);
|
||||
}
|
||||
|
||||
function fetchPgpKey() {
|
||||
const key = keyFingerprintInput.val();
|
||||
const server = keyServerInput.val();
|
||||
|
||||
if (key && server) {
|
||||
$.ajax({
|
||||
url: "/api/v1/service/pgp",
|
||||
data: {"key": key, "server": server},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => { keyBodyInput.text(response.key); },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function importPgpKey() {
|
||||
const key = keyFingerprintInput.val();
|
||||
const server = keyServerInput.val();
|
||||
|
||||
if (key && server) {
|
||||
$.ajax({
|
||||
url: "/api/v1/service/pgp",
|
||||
data: JSON.stringify({key: key, server: server}),
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
success: _ => {
|
||||
keyImportModal.modal("hide");
|
||||
showSuccess("Success", `Key ${key} has been imported`);
|
||||
},
|
||||
error: (jqXHR, _, errorThrown) => {
|
||||
const message = _ => { return `Could not import key ${key} from ${server}`; };
|
||||
showFailure("Action failed", message, jqXHR, errorThrown);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,9 +1,9 @@
|
||||
<div id="loginForm" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div id="login-modal" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form action="/api/v1/login" method="post">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">login</h4>
|
||||
<h4 class="modal-title">Login</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -16,14 +16,36 @@
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-2 col-form-label">password</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="password" type="password" class="form-control" placeholder="enter password" name="password" required>
|
||||
<div class="input-group">
|
||||
<input id="password" type="password" class="form-control" placeholder="enter password" name="password" required>
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="showPassword()"><i id="show-hide-password-btn" class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary">login</button>
|
||||
<button class="btn btn-primary"><i class="bi bi-person"></i> login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const passwordInput = $("#password");
|
||||
const showHidePasswordButton = $("#show-hide-password-btn");
|
||||
|
||||
function showPassword() {
|
||||
if (passwordInput.attr("type") === "password") {
|
||||
passwordInput.attr("type", "text");
|
||||
showHidePasswordButton.removeClass("bi-eye");
|
||||
showHidePasswordButton.addClass("bi-eye-slash");
|
||||
} else {
|
||||
passwordInput.attr("type", "password");
|
||||
showHidePasswordButton.removeClass("bi-eye-slash");
|
||||
showHidePasswordButton.addClass("bi-eye");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,61 +1,78 @@
|
||||
<div id="add-form" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div id="package-add-modal" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">add new packages</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="package" class="col-sm-2 col-form-label">package</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="package-form" type="text" list="known-packages-dlist" autocomplete="off" class="form-control" placeholder="AUR package" name="package" required>
|
||||
<datalist id="known-packages-dlist"></datalist>
|
||||
<form id="package-add-form" onsubmit="return false">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Add new packages</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="package-input" class="col-sm-2 col-form-label">package</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="package-input" type="text" list="known-packages-dlist" autocomplete="off" class="form-control" placeholder="AUR package" name="package" required>
|
||||
<datalist id="known-packages-dlist"></datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<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>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" onclick="packagesAdd()"><i class="bi bi-play"></i> add</button>
|
||||
<button type="submit" class="btn btn-success" onclick="packagesRequest()"><i class="bi bi-plus"></i> request</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const packageInput = $("#package-form");
|
||||
const knownPackages = $("#known-packages-dlist");
|
||||
const packageAddModal = $("#package-add-modal");
|
||||
const packageAddForm = $("#package-add-form");
|
||||
packageAddModal.on("hidden.bs.modal", () => { packageAddForm.trigger("reset"); });
|
||||
|
||||
const packageInput = $("#package-input");
|
||||
const knownPackagesList = $("#known-packages-dlist");
|
||||
packageInput.keyup(() => {
|
||||
clearTimeout(packageInput.data("timeout"));
|
||||
packageInput.data("timeout", setTimeout($.proxy(() => {
|
||||
const value = packageInput.val();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/v1/service/search",
|
||||
data: {"for": value},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const options = response.map(pkg => {
|
||||
const option = document.createElement("option");
|
||||
option.value = pkg.package;
|
||||
option.innerText = `${pkg.package} (${pkg.description})`;
|
||||
return option;
|
||||
});
|
||||
knownPackages.empty().append(options);
|
||||
},
|
||||
})
|
||||
if (value.length >= 3) {
|
||||
$.ajax({
|
||||
url: "/api/v1/service/search",
|
||||
data: {"for": value},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const options = response.map(pkg => {
|
||||
const option = document.createElement("option");
|
||||
option.value = pkg.package;
|
||||
option.innerText = `${pkg.package} (${pkg.description})`;
|
||||
return option;
|
||||
});
|
||||
knownPackagesList.empty().append(options);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, this), 500));
|
||||
});
|
||||
|
||||
function addPackages() {
|
||||
const packages = [packageInput.val()];
|
||||
doPackageAction("/api/v1/service/add", packages);
|
||||
function packagesAdd() {
|
||||
const packages = packageInput.val();
|
||||
if (packages) {
|
||||
packageAddModal.modal("hide");
|
||||
const onSuccess = update => { return `Packages ${update} have been added`; };
|
||||
const onFailure = error => { return `Package addition failed: ${error}`; };
|
||||
doPackageAction("/api/v1/service/add", [packages], onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
|
||||
function requestPackages() {
|
||||
const packages = [packageInput.val()];
|
||||
doPackageAction("/api/v1/service/request", packages);
|
||||
function packagesRequest() {
|
||||
const packages = packageInput.val();
|
||||
if (packages) {
|
||||
packageAddModal.modal("hide");
|
||||
const onSuccess = update => { return `Packages ${update} have been requested`; };
|
||||
const onFailure = error => { return `Package request failed: ${error}`; };
|
||||
doPackageAction("/api/v1/service/request", [packages], onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -0,0 +1,70 @@
|
||||
<div id="package-info-modal" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div id="package-info-modal-header" class="modal-header">
|
||||
<h4 id="package-info" class="modal-title"></h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre class="language-logs"><samp id="package-info-logs-input" class="pre-scrollable language-logs"></samp><button id="logs-copy-btn" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="showLogs()"><i class="bi bi-arrow-clockwise"></i> reload</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i> close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const packageInfoModal = $("#package-info-modal");
|
||||
const packageInfoModalHeader = $("#package-info-modal-header");
|
||||
const packageInfo = $("#package-info");
|
||||
|
||||
const packageInfoLogsInput = $("#package-info-logs-input");
|
||||
const packageInfoLogsCopyButton = $("#logs-copy-btn");
|
||||
|
||||
async function copyLogs() {
|
||||
const logs = packageInfoLogsInput.text();
|
||||
await copyToClipboard(logs, packageInfoLogsCopyButton);
|
||||
}
|
||||
|
||||
function showLogs(packageBase) {
|
||||
const isPackageBaseSet = packageBase !== undefined;
|
||||
if (isPackageBaseSet)
|
||||
packageInfoModal.data("package", packageBase); // set package base as currently used
|
||||
else
|
||||
packageBase = packageInfoModal.data("package"); // read package base from the current window attribute
|
||||
|
||||
const headerClass = status => {
|
||||
if (status === "pending") return ["bg-warning"];
|
||||
if (status === "building") return ["bg-warning"];
|
||||
if (status === "failed") return ["bg-danger", "text-white"];
|
||||
if (status === "success") return ["bg-success", "text-white"];
|
||||
return ["bg-secondary", "text-white"];
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: `/api/v1/packages/${packageBase}/logs`,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
packageInfo.text(`${response.package_base} ${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOString()}`);
|
||||
packageInfoLogsInput.text(response.logs);
|
||||
|
||||
packageInfoModalHeader.removeClass();
|
||||
packageInfoModalHeader.addClass("modal-header");
|
||||
headerClass(response.status.status).forEach((clz) => packageInfoModalHeader.addClass(clz));
|
||||
|
||||
if (isPackageBaseSet) packageInfoModal.modal("show"); // we don't need to show window again
|
||||
},
|
||||
error: (jqXHR, _, errorThrown) => {
|
||||
// show failed modal in case if first time loading
|
||||
if (isPackageBaseSet) {
|
||||
const message = error => { return `Could not load package ${packageBase} logs: ${error}`; };
|
||||
showFailure("Load failure", message, jqXHR, errorThrown);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
@ -0,0 +1,41 @@
|
||||
<div id="package-rebuild-modal" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="package-rebuild-form" onsubmit="return false">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Rebuild depending packages</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="dependency-input" class="col-sm-4 col-form-label">dependency</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="dependency-input" type="text" class="form-control" placeholder="packages dependency" name="package" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" onclick="packagesRebuild()"><i class="bi bi-play"></i> rebuild</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const packageRebuildModal = $("#package-rebuild-modal");
|
||||
const packageRebuildForm = $("#package-rebuild-form");
|
||||
packageRebuildModal.on("hidden.bs.modal", () => { packageRebuildForm.trigger("reset"); });
|
||||
|
||||
const dependencyInput = $("#dependency-input");
|
||||
|
||||
function packagesRebuild() {
|
||||
const packages = dependencyInput.val();
|
||||
if (packages) {
|
||||
packageRebuildModal.modal("hide");
|
||||
const onSuccess = update => { return `Repository rebuild has been run for packages which depend on ${update}`; };
|
||||
const onFailure = error => { return `Repository rebuild failed: ${error}`; };
|
||||
doPackageAction("/api/v1/service/rebuild", [packages], onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,28 +0,0 @@
|
||||
<div id="success-form" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-success">
|
||||
<h4 class="modal-title">success</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Packages update has been run.</p>
|
||||
<ul id="success-details"></ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const successForm = $("#success-form");
|
||||
const successDetails = $("#success-details");
|
||||
successForm.on("hidden.bs.modal", () => { reload(); });
|
||||
|
||||
function showSuccess(details) {
|
||||
successDetails.empty().append(details);
|
||||
successForm.modal("show");
|
||||
}
|
||||
</script>
|
@ -1,34 +1,38 @@
|
||||
<script>
|
||||
const addButton = $("#add-btn");
|
||||
const removeButton = $("#remove-btn");
|
||||
const updateButton = $("#update-btn");
|
||||
const keyImportButton = $("#key-import-btn");
|
||||
const packageAddButton = $("#package-add-btn");
|
||||
const packageRebuildButton = $("#package-rebuild-btn");
|
||||
const packageRemoveButton = $("#package-remove-btn");
|
||||
const packageUpdateButton = $("#package-update-btn");
|
||||
|
||||
const table = $("#packages");
|
||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table",
|
||||
() => {
|
||||
removeButton.prop("disabled", !table.bootstrapTable("getSelections").length);
|
||||
});
|
||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", () => {
|
||||
packageRemoveButton.prop("disabled", !table.bootstrapTable("getSelections").length);
|
||||
});
|
||||
table.on("click-row.bs.table", (self, data, row, cell) => {
|
||||
if (0 === cell || "base" === cell) {
|
||||
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
|
||||
table.bootstrapTable(method, {field: "id", values: [data.id]});
|
||||
} else showLogs(data.id);
|
||||
});
|
||||
|
||||
const architectureBadge = $("#badge-architecture");
|
||||
const repositoryBadge = $("#badge-repository");
|
||||
const statusBadge = $("#badge-status");
|
||||
const versionBadge = $("#badge-version");
|
||||
|
||||
function doPackageAction(uri, packages) {
|
||||
function doPackageAction(uri, packages, successText, failureText) {
|
||||
$.ajax({
|
||||
url: uri,
|
||||
data: JSON.stringify({packages: packages}),
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
success: _ => {
|
||||
const details = packages.map(pkg => {
|
||||
const li = document.createElement("li");
|
||||
li.innerText = pkg;
|
||||
return li;
|
||||
});
|
||||
showSuccess(details);
|
||||
const message = successText(packages.join(", "));
|
||||
showSuccess("Success", message);
|
||||
},
|
||||
error: (jqXHR, _, errorThrown) => {
|
||||
showFailure("Action failed", failureText, jqXHR, errorThrown);
|
||||
},
|
||||
error: (jqXHR, _, errorThrown) => { showFailure(errorThrown); },
|
||||
});
|
||||
}
|
||||
|
||||
@ -36,19 +40,40 @@
|
||||
return table.bootstrapTable("getSelections").map(row => { return row.id; });
|
||||
}
|
||||
|
||||
function removePackages() { doPackageAction("/api/v1/service/remove", getSelection()); }
|
||||
function removePackages() {
|
||||
const onSuccess = update => { return `Packages ${update} have been removed`; };
|
||||
const onFailure = error => { return `Could not remove packages: ${error}`; };
|
||||
doPackageAction("/api/v1/service/remove", getSelection(), onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function updatePackages() { doPackageAction("/api/v1/service/add", getSelection()); }
|
||||
function updatePackages() {
|
||||
const currentSelection = getSelection();
|
||||
const [url, onSuccess] = currentSelection.length === 0
|
||||
? ["/api/v1/service/update", _ => { return "Repository update has been run"; }]
|
||||
: ["/api/v1/service/add", update => { return `Run update for packages ${update}`; }];
|
||||
const onFailure = error => { return `Packages update failed: ${error}`; };
|
||||
doPackageAction(url, currentSelection, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function hideControls(hidden) {
|
||||
addButton.attr("hidden", hidden);
|
||||
removeButton.attr("hidden", hidden);
|
||||
updateButton.attr("hidden", hidden);
|
||||
keyImportButton.attr("hidden", hidden);
|
||||
packageAddButton.attr("hidden", hidden);
|
||||
packageRebuildButton.attr("hidden", hidden);
|
||||
packageRemoveButton.attr("hidden", hidden);
|
||||
packageUpdateButton.attr("hidden", hidden);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
table.bootstrapTable("showLoading");
|
||||
|
||||
const badgeClass = status => {
|
||||
if (status === "pending") return "btn-outline-warning";
|
||||
if (status === "building") return "btn-outline-warning";
|
||||
if (status === "failed") return "btn-outline-danger";
|
||||
if (status === "success") return "btn-outline-success";
|
||||
return "btn-outline-secondary";
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: "/api/v1/packages",
|
||||
type: "GET",
|
||||
@ -95,7 +120,8 @@
|
||||
table.bootstrapTable("hideLoading");
|
||||
} else {
|
||||
// other errors
|
||||
showFailure(errorThrown);
|
||||
const messaga = error => { return `Could not load list of packages: ${error}`; };
|
||||
showFailure("Load failure", messaga, jqXHR, errorThrown);
|
||||
}
|
||||
hideControls(true);
|
||||
},
|
||||
@ -106,39 +132,20 @@
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const badgeColor = status => {
|
||||
if (status === "pending") return "yellow";
|
||||
if (status === "building") return "yellow";
|
||||
if (status === "failed") return "critical";
|
||||
if (status === "success") return "success";
|
||||
return "inactive";
|
||||
};
|
||||
repositoryBadge.text(`${response.repository} ${response.architecture}`);
|
||||
versionBadge.html(`<i class="bi bi-github"></i> ahriman ${safe(response.version)}`);
|
||||
|
||||
architectureBadge
|
||||
.attr("src", `https://img.shields.io/badge/architecture-${response.architecture}-informational`)
|
||||
.attr("alt", response.architecture);
|
||||
repositoryBadge
|
||||
.attr("src", `https://img.shields.io/badge/repository-${response.repository.replace(/-/g, "--")}-informational`)
|
||||
.attr("alt", response.repository);
|
||||
statusBadge
|
||||
.attr("src", `https://img.shields.io/badge/service%20status-${response.status.status}-${badgeColor(response.status.status)}`)
|
||||
.attr("alt", response.status.status)
|
||||
.attr("title", `at ${new Date(1000 * response.status.timestamp).toISOString()}`);
|
||||
versionBadge
|
||||
.attr("src", `https://img.shields.io/badge/version-${response.version}-informational`)
|
||||
.attr("alt", response.version);
|
||||
.popover("dispose")
|
||||
.attr("data-bs-content", `${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOString()}`)
|
||||
.popover();
|
||||
statusBadge.removeClass();
|
||||
statusBadge.addClass("btn");
|
||||
statusBadge.addClass(badgeClass(response.status.status));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function safe(string) {
|
||||
return String(string)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function statusFormat(value) {
|
||||
const cellClass = status => {
|
||||
if (status === "pending") return "table-warning";
|
||||
@ -152,6 +159,7 @@
|
||||
|
||||
$(() => {
|
||||
table.bootstrapTable({});
|
||||
statusBadge.popover();
|
||||
reload();
|
||||
});
|
||||
</script>
|
31
package/share/ahriman/templates/error.jinja2
Normal file
31
package/share/ahriman/templates/error.jinja2
Normal file
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Error</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
|
||||
{% include "utils/style.jinja2" %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||
|
||||
<div class="d-flex flex-row align-items-center">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-12 text-center">
|
||||
<span class="display-1 d-block">{{ code }}</span>
|
||||
<div class="mb-4 lead">{{ reason }}</div>
|
||||
<a class="btn btn-link" style="text-decoration: none" href="/" title="home"><i class="bi bi-house"></i> home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -10,19 +10,22 @@
|
||||
|
||||
<body>
|
||||
|
||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||
|
||||
<div class="container">
|
||||
<h1>Arch linux user repository</h1>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
{% if pgp_key is not none %}
|
||||
<p>This repository is signed with <a href="https://pgp.mit.edu/pks/lookup?search=0x{{ pgp_key }}&fingerprint=on&op=index" title="key search">{{ pgp_key }}</a> by default.</p>
|
||||
<p>This repository is signed with <a href="https://keyserver.ubuntu.com/pks/lookup?search=0x{{ pgp_key }}&fingerprint=on&op=index" title="key search">{{ pgp_key }}</a> by default.</p>
|
||||
{% endif %}
|
||||
|
||||
<pre>$ cat /etc/pacman.conf
|
||||
[{{ repository }}]
|
||||
<p>In order to use this repository edit your <code>/etc/pacman.conf</code> as following:</p>
|
||||
|
||||
<pre class="language-ini"><code id="pacman-conf" class="language-ini">[{{ repository }}]
|
||||
Server = {{ link_path }}
|
||||
SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Package{% if has_package_signed %}Required{% else %}Never{% endif %} TrustedOnly</pre>
|
||||
SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Package{% if has_package_signed %}Required{% else %}Never{% endif %} TrustedOnly</code><button id="copy-btn" type="button" class="btn language-ini" onclick="copyPacmanConf()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
@ -83,16 +86,24 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
||||
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
|
||||
<ul class="nav">
|
||||
{% if homepage is not none %}
|
||||
<li><a class="nav-link" href="{{ homepage }}" title="homepage">homepage</a></li>
|
||||
<li><a class="nav-link" href="{{ homepage }}" title="homepage"><i class="bi bi-house"></i> homepage</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="nav">
|
||||
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources">ahriman</a></li>
|
||||
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||
<script>
|
||||
const pacmanConf = $("#pacman-conf");
|
||||
const pacmanConfCopyButton = $("#copy-btn");
|
||||
|
||||
async function copyPacmanConf() {
|
||||
const conf = pacmanConf.text();
|
||||
await copyToClipboard(conf, pacmanConfCopyButton);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
|
@ -4,9 +4,38 @@
|
||||
|
||||
<script src="https://unpkg.com/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/bootstrap-table@1.20.2/dist/bootstrap-table.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js" integrity="sha384-IDwe1+LCz02ROU9k972gdyvl+AESN10+x7tBKgc9I5HFtuNz0wWnPclzo6p9vxnk" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/bootstrap-table@1.21.1/dist/bootstrap-table.min.js"></script>
|
||||
|
||||
<script src="https://unpkg.com/bootstrap-table@1.20.2/dist/extensions/export/bootstrap-table-export.min.js"></script>
|
||||
<script src="https://unpkg.com/bootstrap-table@1.21.1/dist/extensions/export/bootstrap-table-export.min.js"></script>
|
||||
|
||||
<script src="https://unpkg.com/bootstrap-table@1.20.2/dist/extensions/resizable/bootstrap-table-resizable.js"></script>
|
||||
<script src="https://unpkg.com/bootstrap-table@1.21.1/dist/extensions/resizable/bootstrap-table-resizable.js"></script>
|
||||
|
||||
<script>
|
||||
async function copyToClipboard(text, button) {
|
||||
if (navigator.clipboard === undefined) {
|
||||
const input = document.createElement("textarea");
|
||||
input.innerHTML = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(input);
|
||||
} else {
|
||||
await navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
button.html("<i class=\"bi bi-clipboard-check\"></i> copied");
|
||||
setTimeout(()=> {
|
||||
button.html("<i class=\"bi bi-clipboard\"></i> copy");
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function safe(string) {
|
||||
return String(string)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
</script>
|
||||
|
@ -1,9 +1,26 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.3/font/bootstrap-icons.css" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.20.2/dist/bootstrap-table.min.css" type="text/css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.21.1/dist/bootstrap-table.min.css" type="text/css">
|
||||
|
||||
<link href="https://unpkg.com/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.2.2/dist/cosmo/bootstrap.min.css" integrity="sha256-5t++JZpgVLzo9vF7snO5Qw0y3fA5/NkoJENWB7kpg0E=" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<style>
|
||||
.pre-scrollable {
|
||||
display: block;
|
||||
max-height: 680px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
pre[class*="language-"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
pre[class*="language-"] button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 5px;
|
||||
}
|
||||
</style>
|
||||
|
23
setup.py
23
setup.py
@ -52,29 +52,35 @@ setup(
|
||||
"package/bin/ahriman",
|
||||
],
|
||||
data_files=[
|
||||
# configuration
|
||||
("share/ahriman/settings", [
|
||||
"package/share/ahriman/settings/ahriman.ini",
|
||||
]),
|
||||
("share/ahriman/settings/ahriman.ini.d", [
|
||||
"package/share/ahriman/settings/ahriman.ini.d/logging.ini",
|
||||
]),
|
||||
# systemd files
|
||||
("lib/systemd/system", [
|
||||
"package/lib/systemd/system/ahriman@.service",
|
||||
"package/lib/systemd/system/ahriman@.timer",
|
||||
"package/lib/systemd/system/ahriman-web@.service",
|
||||
]),
|
||||
# templates
|
||||
("share/ahriman/templates", [
|
||||
"package/share/ahriman/templates/build-status.jinja2",
|
||||
"package/share/ahriman/templates/email-index.jinja2",
|
||||
"package/share/ahriman/templates/error.jinja2",
|
||||
"package/share/ahriman/templates/repo-index.jinja2",
|
||||
"package/share/ahriman/templates/shell",
|
||||
"package/share/ahriman/templates/telegram-index.jinja2",
|
||||
]),
|
||||
("share/ahriman/templates/build-status", [
|
||||
"package/share/ahriman/templates/build-status/failed-modal.jinja2",
|
||||
"package/share/ahriman/templates/build-status/alerts.jinja2",
|
||||
"package/share/ahriman/templates/build-status/key-import-modal.jinja2",
|
||||
"package/share/ahriman/templates/build-status/login-modal.jinja2",
|
||||
"package/share/ahriman/templates/build-status/package-add-modal.jinja2",
|
||||
"package/share/ahriman/templates/build-status/success-modal.jinja2",
|
||||
"package/share/ahriman/templates/build-status/package-info-modal.jinja2",
|
||||
"package/share/ahriman/templates/build-status/package-rebuild-modal.jinja2",
|
||||
"package/share/ahriman/templates/build-status/table.jinja2",
|
||||
]),
|
||||
("share/ahriman/templates/static", [
|
||||
@ -84,9 +90,17 @@ setup(
|
||||
"package/share/ahriman/templates/utils/bootstrap-scripts.jinja2",
|
||||
"package/share/ahriman/templates/utils/style.jinja2",
|
||||
]),
|
||||
# man pages
|
||||
("share/man/man1", [
|
||||
"docs/ahriman.1",
|
||||
])
|
||||
]),
|
||||
# shell completions
|
||||
("share/bash-completion/completions", [
|
||||
"docs/completions/bash/_ahriman",
|
||||
]),
|
||||
("share/zsh/site-functions", [
|
||||
"docs/completions/zsh/_ahriman",
|
||||
]),
|
||||
],
|
||||
|
||||
extras_require={
|
||||
@ -100,7 +114,9 @@ setup(
|
||||
"Sphinx",
|
||||
"argparse-manpage",
|
||||
"pydeps",
|
||||
"shtab",
|
||||
"sphinx-argparse",
|
||||
"sphinx-rtd-theme>=1.1.1", # https://stackoverflow.com/a/74355734
|
||||
"sphinxcontrib-napoleon",
|
||||
],
|
||||
# FIXME technically this dependency is required, but in some cases we do not have access to
|
||||
@ -130,6 +146,7 @@ setup(
|
||||
"aiohttp_session",
|
||||
"aiohttp_security",
|
||||
"cryptography",
|
||||
"requests-unixsocket", # required by unix socket support
|
||||
],
|
||||
},
|
||||
)
|
||||
|
@ -68,8 +68,8 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH linux ReposItory MANager",
|
||||
epilog="Argument list can also be read from file by using @ prefix.",
|
||||
fromfile_prefix_chars="@", formatter_class=_formatter)
|
||||
parser.add_argument("-a", "--architecture", help="target architectures (can be used multiple times)",
|
||||
action="append")
|
||||
parser.add_argument("-a", "--architecture", help="target architectures. For several subcommands it can be used "
|
||||
"multiple times", action="append")
|
||||
parser.add_argument("-c", "--configuration", help="configuration path", type=Path, default=Path("/etc/ahriman.ini"))
|
||||
parser.add_argument("--force", help="force run, remove file lock", action="store_true")
|
||||
parser.add_argument("-l", "--lock", help="lock file", type=Path,
|
||||
@ -109,6 +109,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
_set_repo_sign_parser(subparsers)
|
||||
_set_repo_status_update_parser(subparsers)
|
||||
_set_repo_sync_parser(subparsers)
|
||||
_set_repo_tree_parser(subparsers)
|
||||
_set_repo_triggers_parser(subparsers)
|
||||
_set_repo_update_parser(subparsers)
|
||||
_set_shell_parser(subparsers)
|
||||
@ -169,7 +170,7 @@ def _set_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=0)
|
||||
action="count", default=False)
|
||||
parser.set_defaults(handler=handlers.Daemon, dry_run=False, exit_code=False, package=[])
|
||||
return parser
|
||||
|
||||
@ -229,7 +230,7 @@ def _set_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"fail in case if key is not known for build user. This subcommand can be used "
|
||||
"in order to import the PGP key to user keychain.",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("--key-server", help="key server for key import", default="pgp.mit.edu")
|
||||
parser.add_argument("--key-server", help="key server for key import", default="keyserver.ubuntu.com")
|
||||
parser.add_argument("key", help="PGP key to import from public server")
|
||||
parser.set_defaults(handler=handlers.KeyImport, architecture=[""], lock=None, report=False)
|
||||
return parser
|
||||
@ -263,7 +264,7 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=0)
|
||||
action="count", default=False)
|
||||
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
||||
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
|
||||
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
|
||||
@ -305,7 +306,8 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
|
||||
parser.add_argument("package", help="filter status by package base", nargs="*")
|
||||
parser.add_argument("--ahriman", help="get service status itself", action="store_true")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("-i", "--info", help="show additional package information", action="store_true")
|
||||
parser.add_argument("--info", help="show additional package information",
|
||||
action=argparse.BooleanOptionalAction, default=False)
|
||||
parser.add_argument("-s", "--status", help="filter packages by status",
|
||||
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
|
||||
parser.set_defaults(handler=handlers.Status, lock=None, report=False, quiet=True, unsafe=True)
|
||||
@ -483,7 +485,7 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=0)
|
||||
action="count", default=False)
|
||||
parser.set_defaults(handler=handlers.Update, dry_run=True, aur=True, local=True, manual=False)
|
||||
return parser
|
||||
|
||||
@ -632,6 +634,8 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("--build-command", help="build command prefix", default="ahriman")
|
||||
parser.add_argument("--from-configuration", help="path to default devtools pacman configuration",
|
||||
type=Path, default=Path("/usr/share/devtools/pacman-extra.conf"))
|
||||
parser.add_argument("--makeflags-jobs", help="append MAKEFLAGS variable with parallelism set to number of cores",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--multilib", help="add or do not multilib repository",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--packager", help="packager name and email", required=True)
|
||||
@ -640,6 +644,7 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("--sign-target", help="sign options", action="append",
|
||||
type=SignSettings.from_option, choices=enum_values(SignSettings))
|
||||
parser.add_argument("--web-port", help="port of the web service", type=int)
|
||||
parser.add_argument("--web-unix-socket", help="path to unix socket used for interprocess communications", type=Path)
|
||||
parser.set_defaults(handler=handlers.Setup, lock=None, report=False, quiet=True, unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -700,6 +705,23 @@ def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
return parser
|
||||
|
||||
|
||||
def _set_repo_tree_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repository tree subcommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("repo-tree", help="dump repository tree",
|
||||
description="dump repository tree based on packages dependencies",
|
||||
formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Structure, lock=None, report=False, quiet=True)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repository triggers subcommand
|
||||
@ -714,7 +736,7 @@ def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="run triggers on empty build result as configured by settings",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("trigger", help="instead of running all triggers as set by configuration, just process "
|
||||
"specified ones oin order of metion", nargs="*")
|
||||
"specified ones in order of mention", nargs="*")
|
||||
parser.set_defaults(handler=handlers.Triggers)
|
||||
return parser
|
||||
|
||||
@ -745,7 +767,7 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=0)
|
||||
action="count", default=False)
|
||||
parser.set_defaults(handler=handlers.Update)
|
||||
return parser
|
||||
|
||||
@ -760,7 +782,7 @@ def _set_shell_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("shell", help="envoke python shell",
|
||||
parser = root.add_parser("shell", help="invoke python shell",
|
||||
description="drop into python shell while having created application",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("code", help="instead of dropping into shell, just execute the specified code", nargs="?")
|
||||
@ -782,9 +804,10 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser = root.add_parser("user-add", help="create or update user",
|
||||
description="update user for web services with the given password and role. "
|
||||
"In case if password was not entered it will be asked interactively",
|
||||
epilog="In case of first run (i.e. if password salt is not set yet) this action requires "
|
||||
"root privileges because it performs write to filesystem configuration.",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("username", help="username for web service")
|
||||
parser.add_argument("--as-service", help="add user as service user", action="store_true")
|
||||
parser.add_argument("-p", "--password", help="user password. Blank password will be treated as empty password, "
|
||||
"which is in particular must be used for OAuth2 authorization type.")
|
||||
parser.add_argument("-r", "--role", help="user access level",
|
||||
@ -830,7 +853,6 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="remove user from the user mapping and update the configuration",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("username", help="username for web service")
|
||||
parser.add_argument("-s", "--secure", help="set file permissions to user-only", action="store_true")
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, report=False, # nosec
|
||||
password="", quiet=True, unsafe=True)
|
||||
return parser
|
||||
|
@ -37,30 +37,6 @@ class ApplicationPackages(ApplicationProperties):
|
||||
package control class
|
||||
"""
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
|
||||
Returns:
|
||||
Set[str]: list of known packages
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _add_archive(self, source: str, *_: Any) -> None:
|
||||
"""
|
||||
add package from archive
|
||||
@ -147,6 +123,18 @@ class ApplicationPackages(ApplicationProperties):
|
||||
self.database.remote_update(package)
|
||||
# repository packages must not depend on unknown packages, thus we are not going to process dependencies
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
|
||||
Returns:
|
||||
Set[str]: list of known packages
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _process_dependencies(self, local_dir: Path, known_packages: Set[str], without_dependencies: bool) -> None:
|
||||
"""
|
||||
process package dependencies
|
||||
@ -178,6 +166,18 @@ class ApplicationPackages(ApplicationProperties):
|
||||
fn = getattr(self, f"_add_{resolved_source.value}")
|
||||
fn(name, known_packages, without_dependencies)
|
||||
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def remove(self, names: Iterable[str]) -> None:
|
||||
"""
|
||||
remove packages from repository
|
||||
|
@ -19,7 +19,7 @@
|
||||
#
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.repository import Repository
|
||||
|
||||
|
||||
@ -44,10 +44,11 @@ class ApplicationProperties(LazyLogging):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
refresh_pacman_database(int): pacman database syncronization level, ``0`` is disabled
|
||||
refresh_pacman_database(int, optional): pacman database syncronization level, ``0`` is disabled
|
||||
(Default value = 0)
|
||||
"""
|
||||
self.configuration = configuration
|
||||
self.architecture = architecture
|
||||
self.database = SQLite.load(configuration)
|
||||
self.repository = Repository(architecture, configuration, self.database,
|
||||
report=report, unsafe=unsafe, refresh_pacman_database=refresh_pacman_database)
|
||||
self.repository = Repository.load(architecture, configuration, self.database, report=report, unsafe=unsafe,
|
||||
refresh_pacman_database=refresh_pacman_database)
|
||||
|
@ -17,8 +17,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Callable, Iterable, List
|
||||
|
||||
@ -35,18 +33,6 @@ class ApplicationRepository(ApplicationProperties):
|
||||
repository control class
|
||||
"""
|
||||
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None:
|
||||
"""
|
||||
run all clean methods. Warning: some functions might not be available under non-root
|
||||
@ -54,7 +40,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
Args:
|
||||
cache(bool): clear directory with package caches
|
||||
chroot(bool): clear build chroot
|
||||
manual(bool): clear directory with manually added packages
|
||||
manual(bool): clear directory with manually added packages' bases
|
||||
packages(bool): clear directory with built packages
|
||||
pacman(bool): clear directory with pacman databases
|
||||
"""
|
||||
@ -69,6 +55,18 @@ class ApplicationRepository(ApplicationProperties):
|
||||
if pacman:
|
||||
self.repository.clear_pacman()
|
||||
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def sign(self, packages: Iterable[str]) -> None:
|
||||
"""
|
||||
sign packages and repository
|
||||
@ -85,13 +83,10 @@ class ApplicationRepository(ApplicationProperties):
|
||||
if archive.filepath is None:
|
||||
self.logger.warning("filepath is empty for %s", package.base)
|
||||
continue # avoid mypy warning
|
||||
src = self.repository.paths.repository / archive.filepath
|
||||
dst = self.repository.paths.packages / archive.filepath
|
||||
shutil.copy(src, dst)
|
||||
# run generic update function
|
||||
self.update([])
|
||||
self.repository.sign.process_sign_package(archive.filepath, package.base)
|
||||
# sign repository database if set
|
||||
self.repository.sign.process_sign_repository(self.repository.repo.repo_path)
|
||||
# process triggers
|
||||
self.on_result(Result())
|
||||
|
||||
def unknown(self) -> List[str]:
|
||||
@ -150,8 +145,8 @@ class ApplicationRepository(ApplicationProperties):
|
||||
process_update(packages, build_result)
|
||||
|
||||
# process manual packages
|
||||
tree = Tree.load(updates, self.repository.paths, self.database)
|
||||
for num, level in enumerate(tree.levels()):
|
||||
tree = Tree.resolve(updates, self.repository.paths, self.database)
|
||||
for num, level in enumerate(tree):
|
||||
self.logger.info("processing level #%i %s", num, [package.base for package in level])
|
||||
build_result = self.repository.process_build(level)
|
||||
packages = self.repository.packages_built()
|
||||
@ -186,8 +181,12 @@ class ApplicationRepository(ApplicationProperties):
|
||||
|
||||
local_versions = {package.base: package.version for package in self.repository.packages()}
|
||||
updated_packages = [package for _, package in sorted(updates.items())]
|
||||
for package in updated_packages:
|
||||
UpdatePrinter(package, local_versions.get(package.base)).print(
|
||||
verbose=True, log_fn=log_fn, separator=" -> ")
|
||||
|
||||
# reorder updates according to the dependency tree
|
||||
tree = Tree.resolve(updated_packages, self.repository.paths, self.database)
|
||||
for level in tree:
|
||||
for package in level:
|
||||
UpdatePrinter(package, local_versions.get(package.base)).print(
|
||||
verbose=True, log_fn=log_fn, separator=" -> ")
|
||||
|
||||
return updated_packages
|
||||
|
@ -37,6 +37,7 @@ from ahriman.application.handlers.shell import Shell
|
||||
from ahriman.application.handlers.sign import Sign
|
||||
from ahriman.application.handlers.status import Status
|
||||
from ahriman.application.handlers.status_update import StatusUpdate
|
||||
from ahriman.application.handlers.structure import Structure
|
||||
from ahriman.application.handlers.triggers import Triggers
|
||||
from ahriman.application.handlers.unsafe_commands import UnsafeCommands
|
||||
from ahriman.application.handlers.update import Update
|
||||
|
@ -57,7 +57,7 @@ class Backup(Handler):
|
||||
@staticmethod
|
||||
def get_paths(configuration: Configuration) -> Set[Path]:
|
||||
"""
|
||||
extract paths to backup
|
||||
extract paths to back up
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
|
@ -28,6 +28,7 @@ from typing import List, Type
|
||||
from ahriman.application.lock import Lock
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError
|
||||
from ahriman.core.log import Log
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@ -44,6 +45,7 @@ class Handler:
|
||||
be called directly. The recommended way is to call ``execute`` class method, e.g.::
|
||||
|
||||
>>> from ahriman.application.handlers import Add
|
||||
>>>
|
||||
>>> Add.execute(args)
|
||||
"""
|
||||
|
||||
@ -94,7 +96,8 @@ class Handler:
|
||||
bool: True on success, False otherwise
|
||||
"""
|
||||
try:
|
||||
configuration = Configuration.from_path(args.configuration, architecture, args.quiet)
|
||||
configuration = Configuration.from_path(args.configuration, architecture)
|
||||
Log.load(configuration, quiet=args.quiet, report=args.report)
|
||||
with Lock(args, architecture, configuration):
|
||||
cls.run(args, architecture, configuration, report=args.report, unsafe=args.unsafe)
|
||||
return True
|
||||
|
@ -125,7 +125,6 @@ class Patch(Handler):
|
||||
package_base(Optional[str]): package base
|
||||
variables(List[str]): extract patches only for specified PKGBUILD variables
|
||||
exit_code(bool): exit with error on empty search result
|
||||
:
|
||||
"""
|
||||
patches = application.database.patches_list(package_base, variables)
|
||||
Patch.check_if_empty(exit_code, not patches)
|
||||
|
@ -76,5 +76,5 @@ class Rebuild(Handler):
|
||||
List[Package]: list of packages which were stored in database
|
||||
"""
|
||||
if from_database:
|
||||
return application.repository.packages()
|
||||
return [package for (package, _) in application.database.packages_get()]
|
||||
return [package for (package, _) in application.database.packages_get()]
|
||||
return application.repository.packages()
|
||||
|
@ -64,13 +64,15 @@ class Setup(Handler):
|
||||
|
||||
application = Application(architecture, configuration, report=report, unsafe=unsafe)
|
||||
|
||||
Setup.configuration_create_makepkg(args.packager, application.repository.paths)
|
||||
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
|
||||
Setup.executable_create(application.repository.paths, args.build_command, architecture)
|
||||
Setup.configuration_create_devtools(args.build_command, architecture, args.from_configuration,
|
||||
args.multilib, args.repository, application.repository.paths)
|
||||
Setup.configuration_create_sudo(application.repository.paths, args.build_command, architecture)
|
||||
|
||||
application.repository.repo.init()
|
||||
# lazy database sync
|
||||
application.repository.pacman.handle # pylint: disable=pointless-statement
|
||||
|
||||
@staticmethod
|
||||
def build_command(root: Path, prefix: str, architecture: str) -> Path:
|
||||
@ -78,7 +80,7 @@ class Setup(Handler):
|
||||
generate build command name
|
||||
|
||||
Args:
|
||||
root(Path): root directory for the build command (must be root of the reporitory)
|
||||
root(Path): root directory for the build command (must be root of the repository)
|
||||
prefix(str): command prefix in {prefix}-{architecture}-build
|
||||
architecture(str): repository architecture
|
||||
|
||||
@ -118,7 +120,11 @@ class Setup(Handler):
|
||||
section = Configuration.section_name("web", architecture)
|
||||
configuration.set_option(section, "port", str(args.web_port))
|
||||
|
||||
target = include_path / "setup-overrides.ini"
|
||||
if args.web_unix_socket is not None:
|
||||
section = Configuration.section_name("web", architecture)
|
||||
configuration.set_option(section, "unix_socket", str(args.web_unix_socket))
|
||||
|
||||
target = include_path / "00-setup-overrides.ini"
|
||||
with target.open("w") as ahriman_configuration:
|
||||
configuration.write(ahriman_configuration)
|
||||
|
||||
@ -135,7 +141,7 @@ class Setup(Handler):
|
||||
prefix(str): command prefix in {prefix}-{architecture}-build
|
||||
architecture(str): repository architecture
|
||||
source(Path): path to source configuration file
|
||||
multilib(bool): add or do not multilib repository
|
||||
multilib(bool): add or do not multilib repository to the configuration
|
||||
repository(str): repository name
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
"""
|
||||
@ -166,17 +172,23 @@ class Setup(Handler):
|
||||
configuration.write(devtools_configuration)
|
||||
|
||||
@staticmethod
|
||||
def configuration_create_makepkg(packager: str, paths: RepositoryPaths) -> None:
|
||||
def configuration_create_makepkg(packager: str, makeflags_jobs: bool, paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
create configuration for makepkg
|
||||
|
||||
Args:
|
||||
packager(str): packager identifier (e.g. name, email)
|
||||
makeflags_jobs(bool): set MAKEFLAGS variable to number of cores
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
"""
|
||||
|
||||
content = f"PACKAGER='{packager}'\n"
|
||||
if makeflags_jobs:
|
||||
content += """MAKEFLAGS="-j$(nproc)"\n"""
|
||||
|
||||
uid, _ = paths.root_owner
|
||||
home_dir = Path(getpwuid(uid).pw_dir)
|
||||
(home_dir / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8")
|
||||
(home_dir / ".makepkg.conf").write_text(content, encoding="utf8")
|
||||
|
||||
@staticmethod
|
||||
def configuration_create_sudo(paths: RepositoryPaths, prefix: str, architecture: str) -> None:
|
||||
|
@ -56,7 +56,8 @@ class Shell(Handler):
|
||||
# licensed by https://creativecommons.org/licenses/by-sa/3.0
|
||||
path = Path(sys.prefix) / "share" / "ahriman" / "templates" / "shell"
|
||||
StringPrinter(path.read_text(encoding="utf8")).print(verbose=False)
|
||||
# we only want to pass application isntance inside
|
||||
if args.code is None:
|
||||
code.interact(local=locals())
|
||||
code.interact(local={"application": application})
|
||||
else:
|
||||
code.InteractiveConsole(locals=locals()).runcode(args.code)
|
||||
code.InteractiveConsole(locals={"application": application}).runcode(args.code)
|
||||
|
56
src/ahriman/application/handlers/structure.py
Normal file
56
src/ahriman/application/handlers/structure.py
Normal file
@ -0,0 +1,56 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import TreePrinter
|
||||
from ahriman.core.tree import Tree
|
||||
|
||||
|
||||
class Structure(Handler):
|
||||
"""
|
||||
dump repository structure handler
|
||||
"""
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *,
|
||||
report: bool, unsafe: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
architecture(str): repository architecture
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
application = Application(architecture, configuration, report=report, unsafe=unsafe)
|
||||
packages = application.repository.packages()
|
||||
|
||||
tree = Tree.resolve(packages, application.repository.paths, application.database)
|
||||
for num, level in enumerate(tree):
|
||||
TreePrinter(num, level).print(verbose=True, separator=" ")
|
@ -21,11 +21,12 @@ import argparse
|
||||
import getpass
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Type
|
||||
from typing import Optional, Tuple, Type
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import PasswordError
|
||||
from ahriman.core.formatters import UserPrinter
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.user import User
|
||||
@ -54,12 +55,13 @@ class Users(Handler):
|
||||
database = SQLite.load(configuration)
|
||||
|
||||
if args.action == Action.Update:
|
||||
salt = Users.get_salt(configuration)
|
||||
old_salt, salt = Users.get_salt(configuration)
|
||||
user = Users.user_create(args)
|
||||
|
||||
auth_configuration = Users.configuration_get(configuration.include)
|
||||
if old_salt is None:
|
||||
auth_configuration = Users.configuration_get(configuration.include)
|
||||
Users.configuration_create(auth_configuration, salt, args.secure)
|
||||
|
||||
Users.configuration_create(auth_configuration, user, salt, args.as_service, args.secure)
|
||||
database.user_update(user.hash_password(salt))
|
||||
elif args.action == Action.List:
|
||||
users = database.user_list(args.username, args.role)
|
||||
@ -70,22 +72,16 @@ class Users(Handler):
|
||||
database.user_remove(args.username)
|
||||
|
||||
@staticmethod
|
||||
def configuration_create(configuration: Configuration, user: User, salt: str,
|
||||
as_service_user: bool, secure: bool) -> None:
|
||||
def configuration_create(configuration: Configuration, salt: str, secure: bool) -> None:
|
||||
"""
|
||||
enable configuration if it has been disabled
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
user(User): user descriptor
|
||||
salt(str): password hash salt
|
||||
as_service_user(bool): add user as service user, also set password and user to configuration
|
||||
secure(bool): if true then set file permissions to 0o600
|
||||
"""
|
||||
configuration.set_option("auth", "salt", salt)
|
||||
if as_service_user:
|
||||
configuration.set_option("web", "username", user.username)
|
||||
configuration.set_option("web", "password", user.password)
|
||||
Users.configuration_write(configuration, secure)
|
||||
|
||||
@staticmethod
|
||||
@ -99,7 +95,7 @@ class Users(Handler):
|
||||
Returns:
|
||||
Configuration: configuration instance. In case if there are local settings they will be loaded
|
||||
"""
|
||||
target = include_path / "auth.ini"
|
||||
target = include_path / "00-auth.ini"
|
||||
configuration = Configuration()
|
||||
configuration.load(target)
|
||||
|
||||
@ -123,7 +119,7 @@ class Users(Handler):
|
||||
path.chmod(0o600)
|
||||
|
||||
@staticmethod
|
||||
def get_salt(configuration: Configuration, salt_length: int = 20) -> str:
|
||||
def get_salt(configuration: Configuration, salt_length: int = 20) -> Tuple[Optional[str], str]:
|
||||
"""
|
||||
get salt from configuration or create new string
|
||||
|
||||
@ -132,11 +128,12 @@ class Users(Handler):
|
||||
salt_length(int, optional): salt length (Default value = 20)
|
||||
|
||||
Returns:
|
||||
str: current salt
|
||||
Tuple[Optional[str], str]: tuple containing salt from configuration if any and actual salt which must be
|
||||
used for password hash
|
||||
"""
|
||||
if salt := configuration.get("auth", "salt", fallback=None):
|
||||
return salt
|
||||
return User.generate_password(salt_length)
|
||||
return salt, salt
|
||||
return None, User.generate_password(salt_length)
|
||||
|
||||
@staticmethod
|
||||
def user_create(args: argparse.Namespace) -> User:
|
||||
@ -149,7 +146,15 @@ class Users(Handler):
|
||||
Returns:
|
||||
User: built user descriptor
|
||||
"""
|
||||
def read_password() -> str:
|
||||
first_password = getpass.getpass()
|
||||
second_password = getpass.getpass("Repeat password: ")
|
||||
if first_password != second_password:
|
||||
raise PasswordError("passwords don't match")
|
||||
return first_password
|
||||
|
||||
password = args.password
|
||||
if password is None:
|
||||
password = getpass.getpass()
|
||||
password = read_password()
|
||||
|
||||
return User(username=args.username, password=password, access=args.role)
|
||||
|
@ -61,7 +61,7 @@ class Versions(Handler):
|
||||
|
||||
Args:
|
||||
root(str): root package name
|
||||
root_extras(Tuple[str, ...]): extras for the root package (Default value = ())
|
||||
root_extras(Tuple[str, ...], optional): extras for the root package (Default value = ())
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: map of installed dependency to its version
|
||||
|
@ -28,7 +28,7 @@ from typing import Literal, Optional, Type
|
||||
from ahriman import version
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import DuplicateRunError
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.status.client import Client
|
||||
from ahriman.core.util import check_user
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
@ -73,41 +73,7 @@ class Lock(LazyLogging):
|
||||
self.unsafe = args.unsafe
|
||||
|
||||
self.paths = configuration.repository_paths
|
||||
self.reporter = Client.load(configuration) if args.report else Client()
|
||||
|
||||
def __enter__(self) -> Lock:
|
||||
"""
|
||||
default workflow is the following:
|
||||
|
||||
1. Check user UID
|
||||
2. Check if there is lock file
|
||||
3. Check web status watcher status
|
||||
4. Create lock file
|
||||
5. Report to status page if enabled
|
||||
"""
|
||||
self.check_user()
|
||||
self.check_version()
|
||||
self.create()
|
||||
self.reporter.update_self(BuildStatusEnum.Building)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: Optional[Type[Exception]], exc_val: Optional[Exception],
|
||||
exc_tb: TracebackType) -> Literal[False]:
|
||||
"""
|
||||
remove lock file when done
|
||||
|
||||
Args:
|
||||
exc_type(Optional[Type[Exception]]): exception type name if any
|
||||
exc_val(Optional[Exception]): exception raised if any
|
||||
exc_tb(TracebackType): exception traceback if any
|
||||
|
||||
Returns:
|
||||
Literal[False]: always False (do not suppress any exception)
|
||||
"""
|
||||
self.clear()
|
||||
status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed
|
||||
self.reporter.update_self(status)
|
||||
return False
|
||||
self.reporter = Client.load(configuration, report=args.report)
|
||||
|
||||
def check_version(self) -> None:
|
||||
"""
|
||||
@ -145,3 +111,37 @@ class Lock(LazyLogging):
|
||||
self.path.touch(exist_ok=self.force)
|
||||
except FileExistsError:
|
||||
raise DuplicateRunError()
|
||||
|
||||
def __enter__(self) -> Lock:
|
||||
"""
|
||||
default workflow is the following:
|
||||
|
||||
1. Check user UID
|
||||
2. Check if there is lock file
|
||||
3. Check web status watcher status
|
||||
4. Create lock file
|
||||
5. Report to status page if enabled
|
||||
"""
|
||||
self.check_user()
|
||||
self.check_version()
|
||||
self.create()
|
||||
self.reporter.update_self(BuildStatusEnum.Building)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: Optional[Type[Exception]], exc_val: Optional[Exception],
|
||||
exc_tb: TracebackType) -> Literal[False]:
|
||||
"""
|
||||
remove lock file when done
|
||||
|
||||
Args:
|
||||
exc_type(Optional[Type[Exception]]): exception type name if any
|
||||
exc_val(Optional[Exception]): exception raised if any
|
||||
exc_tb(TracebackType): exception traceback if any
|
||||
|
||||
Returns:
|
||||
Literal[False]: always False (do not suppress any exception)
|
||||
"""
|
||||
self.clear()
|
||||
status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed
|
||||
self.reporter.update_self(status)
|
||||
return False
|
||||
|
@ -17,3 +17,82 @@
|
||||
# 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 contextvars import ContextVar
|
||||
from typing import Any, Dict, Iterator, TypeVar
|
||||
|
||||
from ahriman.models.context_key import ContextKey
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class _Context:
|
||||
"""
|
||||
simple ahriman global context implementation
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
default constructor. Must not be used directly
|
||||
"""
|
||||
self._content: Dict[str, Any] = {}
|
||||
|
||||
def get(self, key: ContextKey[T]) -> T:
|
||||
"""
|
||||
get value for the specified key
|
||||
|
||||
Args:
|
||||
key(ContextKey[T]): context key name
|
||||
|
||||
Returns:
|
||||
T: value associated with the key
|
||||
|
||||
Raises:
|
||||
KeyError: in case if the specified context variable was not found
|
||||
ValueError: in case if type of value is not an instance of specified return type
|
||||
"""
|
||||
if key.key not in self._content:
|
||||
raise KeyError(key.key)
|
||||
value = self._content[key.key]
|
||||
if not isinstance(value, key.return_type):
|
||||
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
|
||||
return value
|
||||
|
||||
def set(self, key: ContextKey[T], value: T) -> None:
|
||||
"""
|
||||
set value for the specified key
|
||||
|
||||
Args:
|
||||
key(ContextKey[T]): context key name
|
||||
value(T): context value associated with the specified key
|
||||
|
||||
Raises:
|
||||
KeyError: in case if the specified context variable already exists
|
||||
ValueError: in case if type of value is not an instance of specified return type
|
||||
"""
|
||||
if key.key in self._content:
|
||||
raise KeyError(key.key)
|
||||
if not isinstance(value, key.return_type):
|
||||
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
|
||||
self._content[key.key] = value
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
"""
|
||||
iterate over keys in local storage
|
||||
|
||||
Returns:
|
||||
str: context key iterator
|
||||
"""
|
||||
return iter(self._content)
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
get count of the context variables set
|
||||
|
||||
Returns:
|
||||
int: count of stored context variables
|
||||
"""
|
||||
return len(self._content)
|
||||
|
||||
|
||||
context = ContextVar("context", default=_Context())
|
||||
|
@ -21,10 +21,10 @@ import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore
|
||||
from typing import Generator, Set
|
||||
from typing import Any, Callable, Generator, Set
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@ -36,6 +36,8 @@ class Pacman(LazyLogging):
|
||||
handle(Handle): pyalpm root ``Handle``
|
||||
"""
|
||||
|
||||
handle: Handle
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None:
|
||||
"""
|
||||
default constructor
|
||||
@ -46,6 +48,22 @@ class Pacman(LazyLogging):
|
||||
refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be
|
||||
enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization
|
||||
"""
|
||||
self.__create_handle_fn: Callable[[], Handle] = lambda: self.__create_handle(
|
||||
architecture, configuration, refresh_database=refresh_database)
|
||||
|
||||
def __create_handle(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> Handle:
|
||||
"""
|
||||
create lazy handle function
|
||||
|
||||
Args:
|
||||
architecture(str): repository architecture
|
||||
configuration(Configuration): configuration instance
|
||||
refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be
|
||||
enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization
|
||||
|
||||
Returns:
|
||||
Handle: fully initialized pacman handle
|
||||
"""
|
||||
root = configuration.getpath("alpm", "root")
|
||||
pacman_root = configuration.getpath("alpm", "database")
|
||||
use_ahriman_cache = configuration.getboolean("alpm", "use_ahriman_cache")
|
||||
@ -53,22 +71,25 @@ class Pacman(LazyLogging):
|
||||
paths = configuration.repository_paths
|
||||
database_path = paths.pacman if use_ahriman_cache else pacman_root
|
||||
|
||||
self.handle = Handle(str(root), str(database_path))
|
||||
handle = Handle(str(root), str(database_path))
|
||||
for repository in configuration.getlist("alpm", "repositories"):
|
||||
database = self.database_init(repository, mirror, architecture)
|
||||
self.database_copy(database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
|
||||
database = self.database_init(handle, repository, mirror, architecture)
|
||||
self.database_copy(handle, database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
|
||||
|
||||
if use_ahriman_cache and refresh_database:
|
||||
self.database_sync(refresh_database > 1)
|
||||
self.database_sync(handle, force=refresh_database > 1)
|
||||
|
||||
def database_copy(self, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
|
||||
return handle
|
||||
|
||||
def database_copy(self, handle: Handle, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
|
||||
use_ahriman_cache: bool) -> None:
|
||||
"""
|
||||
copy database from the operating system root to the ahriman local home
|
||||
|
||||
Args:
|
||||
handle(Handle): pacman handle which will be used for database copying
|
||||
database(DB): pacman database instance to be copied
|
||||
pacman_root(Path): operating system pacman's root
|
||||
pacman_root(Path): operating system pacman root
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
use_ahriman_cache(bool): use local ahriman cache instead of system one
|
||||
"""
|
||||
@ -78,7 +99,7 @@ class Pacman(LazyLogging):
|
||||
if not use_ahriman_cache:
|
||||
return
|
||||
# copy root database if no local copy found
|
||||
pacman_db_path = Path(self.handle.dbpath)
|
||||
pacman_db_path = Path(handle.dbpath)
|
||||
if not pacman_db_path.is_dir():
|
||||
return # root directory does not exist yet
|
||||
dst = repository_database(pacman_db_path)
|
||||
@ -92,11 +113,12 @@ class Pacman(LazyLogging):
|
||||
shutil.copy(src, dst)
|
||||
paths.chown(dst)
|
||||
|
||||
def database_init(self, repository: str, mirror: str, architecture: str) -> DB:
|
||||
def database_init(self, handle: Handle, repository: str, mirror: str, architecture: str) -> DB:
|
||||
"""
|
||||
create database instance from pacman handler and set its properties
|
||||
|
||||
Args:
|
||||
handle(Handle): pacman handle which will be used for database initializing
|
||||
repository(str): pacman repository name (e.g. core)
|
||||
mirror(str): arch linux mirror url
|
||||
architecture(str): repository architecture
|
||||
@ -104,21 +126,23 @@ class Pacman(LazyLogging):
|
||||
Returns:
|
||||
DB: loaded pacman database instance
|
||||
"""
|
||||
database: DB = self.handle.register_syncdb(repository, SIG_PACKAGE)
|
||||
self.logger.info("loading pacman databases")
|
||||
database: DB = handle.register_syncdb(repository, SIG_PACKAGE)
|
||||
# replace variables in mirror address
|
||||
database.servers = [mirror.replace("$repo", repository).replace("$arch", architecture)]
|
||||
return database
|
||||
|
||||
def database_sync(self, force: bool) -> None:
|
||||
def database_sync(self, handle: Handle, *, force: bool) -> None:
|
||||
"""
|
||||
sync local database
|
||||
|
||||
Args:
|
||||
handle(Handle): pacman handle which will be used for database sync
|
||||
force(bool): force database syncronization (same as ``pacman -Syy``)
|
||||
"""
|
||||
self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force)
|
||||
transaction = self.handle.init_transaction()
|
||||
for database in self.handle.get_syncdbs():
|
||||
transaction = handle.init_transaction()
|
||||
for database in handle.get_syncdbs():
|
||||
try:
|
||||
database.update(force)
|
||||
except PyalpmError:
|
||||
@ -155,3 +179,22 @@ class Pacman(LazyLogging):
|
||||
result.update(package.provides) # provides list for meta-packages
|
||||
|
||||
return result
|
||||
|
||||
def __getattr__(self, item: str) -> Any:
|
||||
"""
|
||||
pacman handle extractor
|
||||
|
||||
Args:
|
||||
item(str): property name
|
||||
|
||||
Returns:
|
||||
Any: attribute by its name
|
||||
|
||||
Raises:
|
||||
AttributeError: in case if no such attribute found
|
||||
"""
|
||||
if item == "handle":
|
||||
handle = self.__create_handle_fn()
|
||||
setattr(self, item, handle)
|
||||
return handle
|
||||
return super().__getattr__(item) # required for logging attribute
|
||||
|
@ -22,7 +22,7 @@ from __future__ import annotations
|
||||
from typing import Dict, List, Type
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@ class Remote(LazyLogging):
|
||||
"""
|
||||
instance = cls()
|
||||
packages: Dict[str, AURPackage] = {}
|
||||
for term in filter(lambda word: len(word) > 3, keywords):
|
||||
for term in filter(lambda word: len(word) >= 3, keywords):
|
||||
portion = instance.search(term, pacman=pacman)
|
||||
packages = {
|
||||
package.name: package # not mistake to group them by name
|
||||
|
@ -21,7 +21,7 @@ from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from ahriman.core.exceptions import BuildError
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
@ -23,7 +23,7 @@ from typing import Optional, Type
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.auth_settings import AuthSettings
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
@ -55,14 +55,14 @@ class Auth(LazyLogging):
|
||||
def auth_control(self) -> str:
|
||||
"""
|
||||
This workaround is required to make different behaviour for login interface.
|
||||
In case of internal authentication it must provide an interface (modal form) to login with button sends POST
|
||||
In case of internal authentication it must provide an interface (modal form) to log in with button sends POST
|
||||
request. But for an external providers behaviour can be different: e.g. OAuth provider requires sending GET
|
||||
request to external resource
|
||||
|
||||
Returns:
|
||||
str: login control as html code to insert
|
||||
"""
|
||||
return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#loginForm" style="text-decoration: none">login</button>"""
|
||||
return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#login-modal" style="text-decoration: none"><i class="bi bi-box-arrow-in-right"></i> login</button>"""
|
||||
|
||||
@classmethod
|
||||
def load(cls: Type[Auth], configuration: Configuration, database: SQLite) -> Auth:
|
||||
|
@ -29,61 +29,65 @@ except ImportError:
|
||||
__all__ = ["authorized_userid", "check_authorized", "forget", "remember"]
|
||||
|
||||
|
||||
async def authorized_userid(*args: Any) -> Any:
|
||||
async def authorized_userid(*args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
handle aiohttp security methods
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by authorized_userid function
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.authorized_userid(*args) # pylint: disable=no-value-for-parameter
|
||||
return await aiohttp_security.authorized_userid(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
return None
|
||||
|
||||
|
||||
async def check_authorized(*args: Any) -> Any:
|
||||
async def check_authorized(*args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
handle aiohttp security methods
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by check_authorized function
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.check_authorized(*args) # pylint: disable=no-value-for-parameter
|
||||
return await aiohttp_security.check_authorized(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
return None
|
||||
|
||||
|
||||
async def forget(*args: Any) -> Any:
|
||||
async def forget(*args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
handle aiohttp security methods
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by forget function
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.forget(*args) # pylint: disable=no-value-for-parameter
|
||||
return await aiohttp_security.forget(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
return None
|
||||
|
||||
|
||||
async def remember(*args: Any) -> Any:
|
||||
async def remember(*args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
handle disabled auth
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by remember function
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.remember(*args) # pylint: disable=no-value-for-parameter
|
||||
return await aiohttp_security.remember(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
return None
|
||||
|
@ -32,7 +32,7 @@ class Mapping(Auth):
|
||||
user authorization based on mapping from configuration file
|
||||
|
||||
Attributes:
|
||||
salt(str): random generated string to salt passwords
|
||||
salt(str): random generated string to salted password
|
||||
database(SQLite): database instance
|
||||
"""
|
||||
|
||||
|
@ -30,7 +30,7 @@ from ahriman.models.auth_settings import AuthSettings
|
||||
|
||||
class OAuth(Mapping):
|
||||
"""
|
||||
OAuth user authorization.
|
||||
OAuth's user authorization.
|
||||
It is required to create application first and put application credentials.
|
||||
|
||||
Attributes:
|
||||
@ -58,7 +58,7 @@ class OAuth(Mapping):
|
||||
# thus we expect that address is set
|
||||
self.redirect_uri = f"""{configuration.get("web", "address")}/api/v1/login"""
|
||||
self.provider = self.get_provider(configuration.get("auth", "oauth_provider"))
|
||||
# it is list but we will have to convert to string it anyway
|
||||
# it is list, but we will have to convert to string it anyway
|
||||
self.scopes = configuration.get("auth", "oauth_scopes")
|
||||
|
||||
@property
|
||||
@ -69,7 +69,7 @@ class OAuth(Mapping):
|
||||
Returns:
|
||||
str: login control as html code to insert
|
||||
"""
|
||||
return """<a class="nav-link" href="/api/v1/login" title="login via OAuth2">login</a>"""
|
||||
return """<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-google"></i> login</a>"""
|
||||
|
||||
@staticmethod
|
||||
def get_provider(name: str) -> Type[aioauth_client.OAuth2Client]:
|
||||
|
@ -17,14 +17,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import datetime
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.util import check_output, walk
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output, utcnow, walk
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
@ -33,7 +32,7 @@ from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
class Sources(LazyLogging):
|
||||
"""
|
||||
helper to download package sources (PKGBUILD etc)
|
||||
helper to download package sources (PKGBUILD etc...)
|
||||
|
||||
Attributes:
|
||||
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
|
||||
@ -86,7 +85,7 @@ class Sources(LazyLogging):
|
||||
elif remote is not None:
|
||||
instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
|
||||
Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
|
||||
remote.git_url, str(sources_dir), logger=instance.logger)
|
||||
remote.git_url, str(sources_dir), cwd=sources_dir.parent, logger=instance.logger)
|
||||
else:
|
||||
# it will cause an exception later
|
||||
instance.logger.error("%s is not initialized, but no remote provided", sources_dir)
|
||||
@ -161,12 +160,12 @@ class Sources(LazyLogging):
|
||||
str: patch as plain text
|
||||
"""
|
||||
instance = Sources()
|
||||
instance.add(sources_dir, *pattern)
|
||||
instance.add(sources_dir, *pattern, intent_to_add=True)
|
||||
diff = instance.diff(sources_dir)
|
||||
return f"{diff}\n" # otherwise, patch will be broken
|
||||
|
||||
@staticmethod
|
||||
def push(sources_dir: Path, remote: RemoteSource, *pattern: str) -> None:
|
||||
def push(sources_dir: Path, remote: RemoteSource, *pattern: str, commit_author: Optional[str] = None) -> None:
|
||||
"""
|
||||
commit selected changes and push files to the remote repository
|
||||
|
||||
@ -174,19 +173,23 @@ class Sources(LazyLogging):
|
||||
sources_dir(Path): local path to git repository
|
||||
remote(RemoteSource): remote target, branch and url
|
||||
*pattern(str): glob patterns
|
||||
commit_author(Optional[str], optional): commit author in form of git config (i.e. ``user <user@host>``)
|
||||
(Default value = None)
|
||||
"""
|
||||
instance = Sources()
|
||||
instance.add(sources_dir, *pattern)
|
||||
instance.commit(sources_dir)
|
||||
instance.commit(sources_dir, author=commit_author)
|
||||
Sources._check_output("git", "push", remote.git_url, remote.branch, cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
def add(self, sources_dir: Path, *pattern: str) -> None:
|
||||
def add(self, sources_dir: Path, *pattern: str, intent_to_add: bool = False) -> None:
|
||||
"""
|
||||
track found files via git
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to git repository
|
||||
*pattern(str): glob patterns
|
||||
intent_to_add(bool, optional): record only the fact that it will be added later, acts as
|
||||
--intent-to-add git flag (Default value = False)
|
||||
"""
|
||||
# glob directory to find files which match the specified patterns
|
||||
found_files: List[Path] = []
|
||||
@ -196,23 +199,26 @@ class Sources(LazyLogging):
|
||||
return # no additional files found
|
||||
self.logger.info("found matching files %s", found_files)
|
||||
# add them to index
|
||||
Sources._check_output("git", "add", "--intent-to-add",
|
||||
*[str(fn.relative_to(sources_dir)) for fn in found_files],
|
||||
args = ["--intent-to-add"] if intent_to_add else []
|
||||
Sources._check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
|
||||
cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def commit(self, sources_dir: Path, commit_message: Optional[str] = None) -> None:
|
||||
def commit(self, sources_dir: Path, message: Optional[str] = None, author: Optional[str] = None) -> None:
|
||||
"""
|
||||
commit changes
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to git repository
|
||||
commit_message(Optional[str]): optional commit message if any. If none set, message will be generated
|
||||
according to the current timestamp
|
||||
message(Optional[str], optional): optional commit message if any. If none set, message will be generated
|
||||
according to the current timestamp (Default value = None)
|
||||
author(Optional[str], optional): optional commit author if any (Default value = None)
|
||||
"""
|
||||
if commit_message is None:
|
||||
commit_message = f"Autogenerated commit at {datetime.datetime.utcnow()}"
|
||||
Sources._check_output("git", "commit", "--allow-empty", "--message", commit_message,
|
||||
cwd=sources_dir, logger=self.logger)
|
||||
if message is None:
|
||||
message = f"Autogenerated commit at {utcnow()}"
|
||||
args = ["--allow-empty", "--message", message]
|
||||
if author is not None:
|
||||
args.extend(["--author", author])
|
||||
Sources._check_output("git", "commit", *args, cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def diff(self, sources_dir: Path) -> str:
|
||||
"""
|
||||
|
@ -24,7 +24,7 @@ from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import BuildError
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@ -84,10 +84,12 @@ class Task(LazyLogging):
|
||||
user=self.uid)
|
||||
|
||||
# well it is not actually correct, but we can deal with it
|
||||
packages = Task._check_output("makepkg", "--packagelist",
|
||||
exception=BuildError(self.package.base),
|
||||
cwd=sources_dir,
|
||||
logger=self.logger).splitlines()
|
||||
packages = Task._check_output(
|
||||
"makepkg", "--packagelist",
|
||||
exception=BuildError(self.package.base),
|
||||
cwd=sources_dir,
|
||||
logger=self.logger
|
||||
).splitlines()
|
||||
return [Path(package) for package in packages]
|
||||
|
||||
def init(self, sources_dir: Path, database: SQLite) -> None:
|
||||
|
@ -20,12 +20,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import logging
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from logging.config import fileConfig
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple, Type
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||
|
||||
from ahriman.core.exceptions import InitializeError
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@ -38,8 +37,6 @@ class Configuration(configparser.RawConfigParser):
|
||||
Attributes:
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS(List[str]): (class attribute) known sections which can be architecture specific.
|
||||
Required by dump and merging functions
|
||||
DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback)
|
||||
DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
|
||||
SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
|
||||
architecture(Optional[str]): repository architecture
|
||||
path(Optional[Path]): path to root configuration file
|
||||
@ -64,9 +61,6 @@ class Configuration(configparser.RawConfigParser):
|
||||
>>> path, architecture = configuration.check_loaded()
|
||||
"""
|
||||
|
||||
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
|
||||
DEFAULT_LOG_LEVEL = logging.DEBUG
|
||||
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS = ["build", "sign", "web"]
|
||||
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
|
||||
|
||||
@ -75,11 +69,11 @@ class Configuration(configparser.RawConfigParser):
|
||||
default constructor. In the most cases must not be called directly
|
||||
|
||||
Args:
|
||||
allow_no_value(bool): copies ``configparser.RawConfigParser`` behaviour. In case if it is set to ``True``,
|
||||
the keys without values will be allowed
|
||||
allow_no_value(bool, optional): copies ``configparser.RawConfigParser`` behaviour. In case if it is set
|
||||
to ``True``, the keys without values will be allowed (Default value = False)
|
||||
"""
|
||||
configparser.RawConfigParser.__init__(self, allow_no_value=allow_no_value, converters={
|
||||
"list": self.__convert_list,
|
||||
"list": shlex.split,
|
||||
"path": self.__convert_path,
|
||||
})
|
||||
self.architecture: Optional[str] = None
|
||||
@ -117,14 +111,13 @@ class Configuration(configparser.RawConfigParser):
|
||||
return RepositoryPaths(self.getpath("repository", "root"), architecture)
|
||||
|
||||
@classmethod
|
||||
def from_path(cls: Type[Configuration], path: Path, architecture: str, quiet: bool) -> Configuration:
|
||||
def from_path(cls: Type[Configuration], path: Path, architecture: str) -> Configuration:
|
||||
"""
|
||||
constructor with full object initialization
|
||||
|
||||
Args:
|
||||
path(Path): path to root configuration file
|
||||
architecture(str): repository architecture
|
||||
quiet(bool): force disable any log messages
|
||||
|
||||
Returns:
|
||||
Configuration: configuration instance
|
||||
@ -132,42 +125,8 @@ class Configuration(configparser.RawConfigParser):
|
||||
configuration = cls()
|
||||
configuration.load(path)
|
||||
configuration.merge_sections(architecture)
|
||||
configuration.load_logging(quiet)
|
||||
return configuration
|
||||
|
||||
@staticmethod
|
||||
def __convert_list(value: str) -> List[str]:
|
||||
"""
|
||||
convert string value to list of strings
|
||||
|
||||
Args:
|
||||
value(str): string configuration value
|
||||
|
||||
Returns:
|
||||
List[str]: list of string from the parsed string
|
||||
|
||||
Raises:
|
||||
ValueError: in case if option value contains unclosed quotes
|
||||
"""
|
||||
def generator() -> Generator[str, None, None]:
|
||||
quote_mark = None
|
||||
word = ""
|
||||
for char in value:
|
||||
if char in ("'", "\"") and quote_mark is None: # quoted part started, store quote and do nothing
|
||||
quote_mark = char
|
||||
elif char == quote_mark: # quoted part ended, reset quotation
|
||||
quote_mark = None
|
||||
elif char == " " and quote_mark is None: # found space outside the quotation, yield the word
|
||||
yield word
|
||||
word = ""
|
||||
else: # append character to the buffer
|
||||
word += char
|
||||
if quote_mark: # there is unmatched quote
|
||||
raise ValueError(f"unmatched quote in {value}")
|
||||
yield word # sequence done, return whatever we found
|
||||
|
||||
return [word for word in generator() if word]
|
||||
|
||||
@staticmethod
|
||||
def section_name(section: str, suffix: str) -> str:
|
||||
"""
|
||||
@ -281,23 +240,6 @@ class Configuration(configparser.RawConfigParser):
|
||||
except (FileNotFoundError, configparser.NoOptionError, configparser.NoSectionError):
|
||||
pass
|
||||
|
||||
def load_logging(self, quiet: bool) -> None:
|
||||
"""
|
||||
setup logging settings from configuration
|
||||
|
||||
Args:
|
||||
quiet(bool): force disable any log messages
|
||||
"""
|
||||
try:
|
||||
path = self.logging_path
|
||||
fileConfig(path)
|
||||
except Exception:
|
||||
logging.basicConfig(filename=None, format=self.DEFAULT_LOG_FORMAT,
|
||||
level=self.DEFAULT_LOG_LEVEL)
|
||||
logging.exception("could not load logging from configuration, fallback to stderr")
|
||||
if quiet:
|
||||
logging.disable(logging.WARNING) # only print errors here
|
||||
|
||||
def merge_sections(self, architecture: str) -> None:
|
||||
"""
|
||||
merge architecture specific sections into main configuration
|
||||
@ -310,8 +252,8 @@ class Configuration(configparser.RawConfigParser):
|
||||
# get overrides
|
||||
specific = self.section_name(section, architecture)
|
||||
if self.has_section(specific):
|
||||
# if there is no such section it means that there is no overrides for this arch
|
||||
# but we anyway will have to delete sections for others archs
|
||||
# if there is no such section it means that there is no overrides for this arch,
|
||||
# but we anyway will have to delete sections for others architectures
|
||||
for key, value in self[specific].items():
|
||||
self.set_option(section, key, value)
|
||||
# remove any arch specific section
|
||||
|
@ -27,7 +27,7 @@ from typing import List, Type
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database.data import migrate_data
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.migration import Migration
|
||||
from ahriman.models.migration_result import MigrationResult
|
||||
|
||||
|
35
src/ahriman/core/database/migrations/m004_logs.py
Normal file
35
src/ahriman/core/database/migrations/m004_logs.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
__all__ = ["steps"]
|
||||
|
||||
|
||||
steps = [
|
||||
"""
|
||||
create table logs (
|
||||
package_base text not null,
|
||||
process_id integer not null,
|
||||
created real not null,
|
||||
record text
|
||||
)
|
||||
""",
|
||||
"""
|
||||
create index logs_package_base_process_id on logs (package_base, process_id)
|
||||
""",
|
||||
]
|
@ -21,5 +21,6 @@ from ahriman.core.database.operations.operations import Operations
|
||||
|
||||
from ahriman.core.database.operations.auth_operations import AuthOperations
|
||||
from ahriman.core.database.operations.build_operations import BuildOperations
|
||||
from ahriman.core.database.operations.logs_operations import LogsOperations
|
||||
from ahriman.core.database.operations.package_operations import PackageOperations
|
||||
from ahriman.core.database.operations.patch_operations import PatchOperations
|
||||
|
@ -26,7 +26,7 @@ from ahriman.models.package import Package
|
||||
|
||||
class BuildOperations(Operations):
|
||||
"""
|
||||
operations for main functions
|
||||
operations for build queue functions
|
||||
"""
|
||||
|
||||
def build_queue_clear(self, package_base: Optional[str]) -> None:
|
||||
|
102
src/ahriman/core/database/operations/logs_operations.py
Normal file
102
src/ahriman/core/database/operations/logs_operations.py
Normal file
@ -0,0 +1,102 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from sqlite3 import Connection
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.core.database.operations import Operations
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
|
||||
|
||||
class LogsOperations(Operations):
|
||||
"""
|
||||
logs operations
|
||||
"""
|
||||
|
||||
def logs_get(self, package_base: str) -> str:
|
||||
"""
|
||||
extract logs for specified package base
|
||||
|
||||
Args:
|
||||
package_base(str): package base to extract logs
|
||||
|
||||
Return:
|
||||
str: full package log
|
||||
"""
|
||||
def run(connection: Connection) -> List[str]:
|
||||
return [
|
||||
f"""[{pretty_datetime(row["created"])}] {row["record"]}"""
|
||||
for row in connection.execute(
|
||||
"""
|
||||
select created, record from logs where package_base = :package_base
|
||||
order by created
|
||||
""",
|
||||
{"package_base": package_base})
|
||||
]
|
||||
|
||||
records = self.with_connection(run)
|
||||
return "\n".join(records)
|
||||
|
||||
def logs_insert(self, log_record_id: LogRecordId, created: float, record: str) -> None:
|
||||
"""
|
||||
write new log record to database
|
||||
|
||||
Args:
|
||||
log_record_id(LogRecordId): current log record id
|
||||
created(float): log created timestamp from log record attribute
|
||||
record(str): log record
|
||||
"""
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
insert into logs
|
||||
(package_base, process_id, created, record)
|
||||
values
|
||||
(:package_base, :process_id, :created, :record)
|
||||
""",
|
||||
dict(
|
||||
package_base=log_record_id.package_base,
|
||||
process_id=log_record_id.process_id,
|
||||
created=created,
|
||||
record=record
|
||||
)
|
||||
)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def logs_remove(self, package_base: str, current_process_id: Optional[int]) -> None:
|
||||
"""
|
||||
remove log records for the specified package
|
||||
|
||||
Args:
|
||||
package_base(str): package base to remove logs
|
||||
current_process_id(Optional[int]): current process id. If set it will remove only logs belonging to another
|
||||
process
|
||||
"""
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
delete from logs
|
||||
where package_base = :package_base and (:process_id is null or process_id <> :process_id)
|
||||
""",
|
||||
{"package_base": package_base, "process_id": current_process_id}
|
||||
)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
@ -22,7 +22,8 @@ import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Tuple, TypeVar, Callable
|
||||
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
@ -27,10 +27,11 @@ from typing import Type
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database.migrations import Migrations
|
||||
from ahriman.core.database.operations import AuthOperations, BuildOperations, PackageOperations, PatchOperations
|
||||
from ahriman.core.database.operations import AuthOperations, BuildOperations, LogsOperations, PackageOperations, \
|
||||
PatchOperations
|
||||
|
||||
|
||||
class SQLite(AuthOperations, BuildOperations, PackageOperations, PatchOperations):
|
||||
class SQLite(AuthOperations, BuildOperations, LogsOperations, PackageOperations, PatchOperations):
|
||||
"""
|
||||
wrapper for sqlite3 database
|
||||
|
||||
|
@ -179,6 +179,21 @@ class PathError(ValueError):
|
||||
ValueError.__init__(self, f"Path `{path}` does not belong to repository root `{root}`")
|
||||
|
||||
|
||||
class PasswordError(ValueError):
|
||||
"""
|
||||
exception which will be raised in case of password related errors
|
||||
"""
|
||||
|
||||
def __init__(self, details: Any) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
details(Any); error details
|
||||
"""
|
||||
ValueError.__init__(self, f"Password error: {details}")
|
||||
|
||||
|
||||
class ReportError(RuntimeError):
|
||||
"""
|
||||
report generation exception
|
||||
|
@ -26,6 +26,7 @@ from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
|
||||
from ahriman.core.formatters.package_printer import PackagePrinter
|
||||
from ahriman.core.formatters.patch_printer import PatchPrinter
|
||||
from ahriman.core.formatters.status_printer import StatusPrinter
|
||||
from ahriman.core.formatters.tree_printer import TreePrinter
|
||||
from ahriman.core.formatters.update_printer import UpdatePrinter
|
||||
from ahriman.core.formatters.user_printer import UserPrinter
|
||||
from ahriman.core.formatters.version_printer import VersionPrinter
|
||||
|
@ -33,8 +33,7 @@ class Printer:
|
||||
|
||||
Args:
|
||||
verbose(bool): print all fields
|
||||
log_fn(Callable[[str]): logger function to log data
|
||||
None]: (Default value = print)
|
||||
log_fn(Callable[[str], None]): logger function to log data (Default value = print)
|
||||
separator(str, optional): separator for property name and property value (Default value = ": ")
|
||||
"""
|
||||
if (title := self.title()) is not None:
|
||||
|
53
src/ahriman/core/formatters/tree_printer.py
Normal file
53
src/ahriman/core/formatters/tree_printer.py
Normal file
@ -0,0 +1,53 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import Iterable, List
|
||||
|
||||
from ahriman.core.formatters import StringPrinter
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class TreePrinter(StringPrinter):
|
||||
"""
|
||||
print content of the package tree level
|
||||
|
||||
Attributes:
|
||||
packages(Iterable[Package]): packages which belong to this level
|
||||
"""
|
||||
|
||||
def __init__(self, level: int, packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
level(int): dependencies tree level
|
||||
packages(Iterable[Package]): packages which belong to this level
|
||||
"""
|
||||
StringPrinter.__init__(self, f"level {level}")
|
||||
self.packages = packages
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
|
||||
Returns:
|
||||
List[Property]: list of content properties
|
||||
"""
|
||||
return [Property(package.base, package.version, is_required=True) for package in self.packages]
|
@ -25,7 +25,7 @@ from tempfile import TemporaryDirectory
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import GitRemoteError
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import walk
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
|
@ -25,6 +25,9 @@ from ahriman.core.triggers import Trigger
|
||||
class RemotePullTrigger(Trigger):
|
||||
"""
|
||||
trigger based on pulling PKGBUILDs before the actions
|
||||
|
||||
Attributes:
|
||||
targets(List[str]): git remote target list
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
|
@ -25,8 +25,9 @@ from typing import Generator
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import GitRemoteError
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
@ -38,17 +39,22 @@ class RemotePush(LazyLogging):
|
||||
sync PKGBUILDs to remote repository after actions
|
||||
|
||||
Attributes:
|
||||
commit_author(Optional[str]): optional commit author in form of git config (i.e. ``user <user@host>``)
|
||||
database(SQLite): database instance
|
||||
remote_source(RemoteSource): repository remote source (remote pull url and branch)
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: Configuration, section: str) -> None:
|
||||
def __init__(self, configuration: Configuration, database: SQLite, section: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
remote_push_trigger.py
|
||||
database(SQLite): database instance
|
||||
section(str): settings section name
|
||||
"""
|
||||
self.database = database
|
||||
self.commit_author = configuration.get(section, "commit_author", fallback=None)
|
||||
self.remote_source = RemoteSource(
|
||||
git_url=configuration.get(section, "push_url"),
|
||||
web_url="",
|
||||
@ -57,8 +63,7 @@ class RemotePush(LazyLogging):
|
||||
source=PackageSource.Local,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def package_update(package: Package, target_dir: Path) -> str:
|
||||
def package_update(self, package: Package, target_dir: Path) -> str:
|
||||
"""
|
||||
clone specified package and update its content in cloned PKGBUILD repository
|
||||
|
||||
@ -73,17 +78,19 @@ class RemotePush(LazyLogging):
|
||||
# firstly, we need to remove old data to make sure that removed files are not tracked anymore...
|
||||
package_target_dir = target_dir / package.base
|
||||
shutil.rmtree(package_target_dir, ignore_errors=True)
|
||||
# ...secondly, we copy whole tree...
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
|
||||
Sources.fetch(clone_dir, package.remote)
|
||||
shutil.copytree(clone_dir, package_target_dir)
|
||||
# ...secondly, we clone whole tree...
|
||||
Sources.fetch(package_target_dir, package.remote)
|
||||
# ...and last, but not least, we remove the dot-git directory...
|
||||
shutil.rmtree(package_target_dir / ".git", ignore_errors=True)
|
||||
for git_file in package_target_dir.glob(".git*"):
|
||||
shutil.rmtree(package_target_dir / git_file)
|
||||
# ...copy all patches...
|
||||
for patch in self.database.patches_get(package.base):
|
||||
filename = f"ahriman-{package.base}.patch" if patch.key is None else f"ahriman-{patch.key}.patch"
|
||||
patch.write(package_target_dir / filename)
|
||||
# ...and finally return path to the copied directory
|
||||
return package.base
|
||||
|
||||
@staticmethod
|
||||
def packages_update(result: Result, target_dir: Path) -> Generator[str, None, None]:
|
||||
def packages_update(self, result: Result, target_dir: Path) -> Generator[str, None, None]:
|
||||
"""
|
||||
update all packages from the build result
|
||||
|
||||
@ -95,7 +102,7 @@ class RemotePush(LazyLogging):
|
||||
str: path to updated files
|
||||
"""
|
||||
for package in result.success:
|
||||
yield RemotePush.package_update(package, target_dir)
|
||||
yield self.package_update(package, target_dir)
|
||||
|
||||
def run(self, result: Result) -> None:
|
||||
"""
|
||||
@ -107,7 +114,8 @@ class RemotePush(LazyLogging):
|
||||
try:
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
|
||||
Sources.fetch(clone_dir, self.remote_source)
|
||||
Sources.push(clone_dir, self.remote_source, *RemotePush.packages_update(result, clone_dir))
|
||||
Sources.push(clone_dir, self.remote_source, *self.packages_update(result, clone_dir),
|
||||
commit_author=self.commit_author)
|
||||
except Exception:
|
||||
self.logger.exception("git push failed")
|
||||
raise GitRemoteError()
|
||||
|
@ -19,9 +19,12 @@
|
||||
#
|
||||
from typing import Iterable
|
||||
|
||||
from ahriman.core import context
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.gitremote.remote_push import RemotePush
|
||||
from ahriman.core.triggers import Trigger
|
||||
from ahriman.models.context_key import ContextKey
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
@ -29,6 +32,9 @@ from ahriman.models.result import Result
|
||||
class RemotePushTrigger(Trigger):
|
||||
"""
|
||||
trigger for syncing PKGBUILDs to remote repository
|
||||
|
||||
Attributes:
|
||||
targets(List[str]): git remote target list
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
@ -49,8 +55,14 @@ class RemotePushTrigger(Trigger):
|
||||
Args:
|
||||
result(Result): build result
|
||||
packages(Iterable[Package]): list of all available packages
|
||||
|
||||
Raises:
|
||||
GitRemoteError: if database is not set in context
|
||||
"""
|
||||
ctx = context.get()
|
||||
database = ctx.get(ContextKey("database", SQLite))
|
||||
|
||||
for target in self.targets:
|
||||
section, _ = self.configuration.gettype(target, self.architecture)
|
||||
runner = RemotePush(self.configuration, section)
|
||||
runner = RemotePush(self.configuration, database, section)
|
||||
runner.run(result)
|
||||
|
21
src/ahriman/core/log/__init__.py
Normal file
21
src/ahriman/core/log/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from ahriman.core.log.lazy_logging import LazyLogging
|
||||
from ahriman.core.log.log import Log
|
60
src/ahriman/core/log/filtered_access_logger.py
Normal file
60
src/ahriman/core/log/filtered_access_logger.py
Normal file
@ -0,0 +1,60 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import re
|
||||
|
||||
from aiohttp.web import AccessLogger, BaseRequest, StreamResponse
|
||||
|
||||
|
||||
class FilteredAccessLogger(AccessLogger):
|
||||
"""
|
||||
access logger implementation with log filter enabled
|
||||
|
||||
Attributes:
|
||||
LOG_PATH_REGEX(re.Pattern): (class attribute) regex for logs uri
|
||||
"""
|
||||
|
||||
# official packages have only ``[A-Za-z0-9_.+-]`` regex
|
||||
LOG_PATH_REGEX = re.compile(r"^/api/v1/packages/[A-Za-z0-9_.+%-]+/logs$")
|
||||
|
||||
@staticmethod
|
||||
def is_logs_post(request: BaseRequest) -> bool:
|
||||
"""
|
||||
check if request looks lie logs posting
|
||||
|
||||
Args:
|
||||
request(BaseRequest): http reqeust descriptor
|
||||
|
||||
Returns:
|
||||
bool: True in case if request looks like logs positing and False otherwise
|
||||
"""
|
||||
return request.method == "POST" and FilteredAccessLogger.LOG_PATH_REGEX.match(request.path) is not None
|
||||
|
||||
def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:
|
||||
"""
|
||||
access log with enabled filter by request path
|
||||
|
||||
Args:
|
||||
request(BaseRequest): http reqeust descriptor
|
||||
response(StreamResponse): streaming response object
|
||||
time(float):
|
||||
"""
|
||||
if self.is_logs_post(request):
|
||||
return
|
||||
AccessLogger.log(self, request, response, time)
|
84
src/ahriman/core/log/http_log_handler.py
Normal file
84
src/ahriman/core/log/http_log_handler.py
Normal file
@ -0,0 +1,84 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
class HttpLogHandler(logging.Handler):
|
||||
"""
|
||||
handler for the http logging. Because default ``logging.handlers.HTTPHandler`` does not support cookies
|
||||
authorization, we have to implement own handler which overrides the ``logging.handlers.HTTPHandler.emit`` method
|
||||
|
||||
Attributes:
|
||||
reporter(Client): build status reporter instance
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: Configuration, *, report: bool) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
# we don't really care about those parameters because they will be handled by the reporter
|
||||
logging.Handler.__init__(self)
|
||||
|
||||
# client has to be importer here because of circular imports
|
||||
from ahriman.core.status.client import Client
|
||||
self.reporter = Client.load(configuration, report=report)
|
||||
|
||||
@classmethod
|
||||
def load(cls, configuration: Configuration, *, report: bool) -> HttpLogHandler:
|
||||
"""
|
||||
install logger. This function creates handler instance and adds it to the handler list in case if no other
|
||||
http handler found
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
root = logging.getLogger()
|
||||
if (handler := next((handler for handler in root.handlers if isinstance(handler, cls)), None)) is not None:
|
||||
return handler # there is already registered instance
|
||||
|
||||
handler = cls(configuration, report=report)
|
||||
root.addHandler(handler)
|
||||
|
||||
return handler
|
||||
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
"""
|
||||
emit log records using reporter client
|
||||
|
||||
Args:
|
||||
record(logging.LogRecord): log record to log
|
||||
"""
|
||||
package_base = getattr(record, "package_base", None)
|
||||
if package_base is None:
|
||||
return # in case if no package base supplied we need just skip log message
|
||||
|
||||
try:
|
||||
self.reporter.logs(package_base, record)
|
||||
except Exception:
|
||||
self.handleError(record)
|
@ -17,9 +17,10 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, Generator
|
||||
|
||||
|
||||
class LazyLogging:
|
||||
@ -32,12 +33,68 @@ class LazyLogging:
|
||||
|
||||
logger: logging.Logger
|
||||
|
||||
@property
|
||||
def logger_name(self) -> str:
|
||||
"""
|
||||
extract logger name for the class
|
||||
|
||||
Returns:
|
||||
str: logger name as combination of module name and class name
|
||||
"""
|
||||
clazz = self.__class__
|
||||
prefix = "" if clazz.__module__ is None else f"{clazz.__module__}."
|
||||
return f"{prefix}{clazz.__qualname__}"
|
||||
|
||||
@staticmethod
|
||||
def _package_logger_reset() -> None:
|
||||
"""
|
||||
reset package logger to empty one
|
||||
"""
|
||||
logging.setLogRecordFactory(logging.LogRecord)
|
||||
|
||||
@staticmethod
|
||||
def _package_logger_set(package_base: str) -> None:
|
||||
"""
|
||||
set package base as extra info to the logger
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
"""
|
||||
current_factory = logging.getLogRecordFactory()
|
||||
|
||||
def package_record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
|
||||
record = current_factory(*args, **kwargs)
|
||||
record.package_base = package_base
|
||||
return record
|
||||
|
||||
logging.setLogRecordFactory(package_record_factory)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_package_context(self, package_base: str) -> Generator[None, None, None]:
|
||||
"""
|
||||
execute function while setting package context
|
||||
|
||||
Args:
|
||||
package_base(str): package base to set context in
|
||||
|
||||
Examples:
|
||||
This function is designed to be called as context manager with ``package_base`` argument, e.g.:
|
||||
|
||||
>>> with self.in_package_context(package.base):
|
||||
>>> build_package(package)
|
||||
"""
|
||||
try:
|
||||
self._package_logger_set(package_base)
|
||||
yield
|
||||
finally:
|
||||
self._package_logger_reset()
|
||||
|
||||
def __getattr__(self, item: str) -> Any:
|
||||
"""
|
||||
logger extractor
|
||||
|
||||
Args:
|
||||
item(str) property name:
|
||||
item(str): property name
|
||||
|
||||
Returns:
|
||||
Any: attribute by its name
|
||||
@ -50,15 +107,3 @@ class LazyLogging:
|
||||
setattr(self, item, logger)
|
||||
return logger
|
||||
raise AttributeError(f"'{self.__class__.__qualname__}' object has no attribute '{item}'")
|
||||
|
||||
@property
|
||||
def logger_name(self) -> str:
|
||||
"""
|
||||
extract logger name for the class
|
||||
|
||||
Returns:
|
||||
str: logger name as combination of module name and class name
|
||||
"""
|
||||
clazz = self.__class__
|
||||
prefix = "" if clazz.__module__ is None else f"{clazz.__module__}."
|
||||
return f"{prefix}{clazz.__qualname__}"
|
61
src/ahriman/core/log/log.py
Normal file
61
src/ahriman/core/log/log.py
Normal file
@ -0,0 +1,61 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
|
||||
from logging.config import fileConfig
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log.http_log_handler import HttpLogHandler
|
||||
|
||||
|
||||
class Log:
|
||||
"""
|
||||
simple static method class which setups application loggers
|
||||
|
||||
Attributes:
|
||||
DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback)
|
||||
DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
|
||||
"""
|
||||
|
||||
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
|
||||
DEFAULT_LOG_LEVEL = logging.DEBUG
|
||||
|
||||
@staticmethod
|
||||
def load(configuration: Configuration, *, quiet: bool, report: bool) -> None:
|
||||
"""
|
||||
setup logging settings from configuration
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
quiet(bool): force disable any log messages
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
try:
|
||||
path = configuration.logging_path
|
||||
fileConfig(path)
|
||||
except Exception:
|
||||
logging.basicConfig(filename=None, format=Log.DEFAULT_LOG_FORMAT,
|
||||
level=Log.DEFAULT_LOG_LEVEL)
|
||||
logging.exception("could not load logging from configuration, fallback to stderr")
|
||||
|
||||
HttpLogHandler.load(configuration, report=report)
|
||||
|
||||
if quiet:
|
||||
logging.disable(logging.WARNING) # only print errors here
|
@ -17,7 +17,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import datetime
|
||||
import smtplib
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
@ -27,7 +26,7 @@ from typing import Dict, Iterable
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.core.util import pretty_datetime, utcnow
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
from ahriman.models.smtp_ssl_settings import SmtpSSLSettings
|
||||
@ -86,7 +85,7 @@ class Email(Report, JinjaTemplate):
|
||||
message = MIMEMultipart()
|
||||
message["From"] = self.sender
|
||||
message["To"] = ", ".join(self.receivers)
|
||||
message["Subject"] = f"{self.name} build report at {pretty_datetime(datetime.datetime.utcnow())}"
|
||||
message["Subject"] = f"{self.name} build report at {pretty_datetime(utcnow())}"
|
||||
|
||||
message.attach(MIMEText(text, "html"))
|
||||
for filename, content in attachment.items():
|
||||
|
@ -23,7 +23,7 @@ from typing import Iterable, Type
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import ReportError
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.report_settings import ReportSettings
|
||||
from ahriman.models.result import Result
|
||||
@ -54,7 +54,7 @@ class Report(LazyLogging):
|
||||
>>> except Exception as exception:
|
||||
>>> handle_exceptions(exception)
|
||||
>>>
|
||||
>>> report.run([], Result())
|
||||
>>> report.run(Result(), [])
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
|
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import requests # technically we could use python-telegram-bot, but it is just a single request, cmon
|
||||
import requests # technically we could use python-telegram-bot, but it is just a single request, c'mon
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user