mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-01 16:15:49 +00:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
e441d93a56 | |||
664b6369bb | |||
4f6bd29ff4 | |||
f6d9ea480a | |||
08f62842ba | |||
1912790ccc | |||
cf3c48ffeb | |||
6633766cc3 | |||
f73d1eb424 | |||
87ab3683ad | |||
9c2f73af8c | |||
31f551bdf2 | |||
cbbe2b01e4 | |||
cd361a483d | |||
cee4fd4cce | |||
18daecaac7 | |||
03c298c762 | |||
79d4a488a0 | |||
22847aac68 | |||
75b9a96798 | |||
a0cfb7561d | |||
f4131b8cd7 | |||
9f134e37b6 | |||
07b7689701 | |||
47de715d7d | |||
5674b7b388 | |||
f6e57a411c | |||
38e48b1571 | |||
d492c968ea | |||
a11fd188a2 | |||
2431d5de0e |
@ -1 +1 @@
|
|||||||
skips: ['B101', 'B105', 'B404']
|
skips: ['B101', 'B105', 'B106', 'B404']
|
10
.github/ISSUE_TEMPLATE/bug-report.md
vendored
10
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -11,9 +11,9 @@ assignees: ''
|
|||||||
|
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
### Steps to Reproduce
|
### Steps to reproduce
|
||||||
|
|
||||||
Steps to reproduce the behavior (commands, environment etc)
|
Steps to reproduce the behavior (commands, environment etc).
|
||||||
|
|
||||||
### Expected behavior
|
### Expected behavior
|
||||||
|
|
||||||
@ -21,4 +21,8 @@ A clear and concise description of what you expected to happen.
|
|||||||
|
|
||||||
### Logs
|
### Logs
|
||||||
|
|
||||||
Add logs to help explain your problem. Logs to stderr can be generated by using `--no-log` command line option.
|
Add logs to help explain your problem. By default, the application writes logs into `/dev/log` which is usually default systemd journal and can be accessed by `journalctl` command.
|
||||||
|
|
||||||
|
You can also attach any additional information which can be helpful, e.g. configuration used by the application (be aware of passwords and other secrets if any); it can be generated by using `ahriman config` command.
|
||||||
|
|
||||||
|
It is also sometimes useful to have information about installed packages which can be accessed by `ahriman version` command.
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -13,7 +13,7 @@ Brief description of the feature required
|
|||||||
|
|
||||||
### Cause of the feature request
|
### Cause of the feature request
|
||||||
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
A clear and concise description of what the problem is. E.g. I'm always frustrated when [...]
|
||||||
|
|
||||||
### Proposed changes and/or features
|
### Proposed changes and/or features
|
||||||
|
|
||||||
|
2
.github/workflows/setup.sh
vendored
2
.github/workflows/setup.sh
vendored
@ -8,7 +8,7 @@ echo -e '[arcanisrepo]\nServer = http://repo.arcanis.me/$arch\nSigLevel = Never'
|
|||||||
# refresh the image
|
# refresh the image
|
||||||
pacman --noconfirm -Syu
|
pacman --noconfirm -Syu
|
||||||
# main dependencies
|
# main dependencies
|
||||||
pacman --noconfirm -Sy base-devel devtools git pyalpm python-aur python-passlib python-srcinfo sudo
|
pacman --noconfirm -Sy base-devel devtools git pyalpm python-aur python-passlib python-setuptools python-srcinfo sudo
|
||||||
# make dependencies
|
# make dependencies
|
||||||
pacman --noconfirm -Sy python-build python-installer python-wheel
|
pacman --noconfirm -Sy python-build python-installer python-wheel
|
||||||
# optional dependencies
|
# optional dependencies
|
||||||
|
2
.github/workflows/tests.sh
vendored
2
.github/workflows/tests.sh
vendored
@ -4,7 +4,7 @@
|
|||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
# install dependencies
|
# install dependencies
|
||||||
pacman --noconfirm -Syu base-devel python-pip python-tox
|
pacman --noconfirm -Syu base-devel python-pip python-setuptools python-tox
|
||||||
|
|
||||||
# run test and check targets
|
# run test and check targets
|
||||||
make check tests
|
make check tests
|
||||||
|
73
.pylintrc
73
.pylintrc
@ -60,17 +60,7 @@ confidence=
|
|||||||
# --enable=similarities". If you want to run only the classes checker, but have
|
# --enable=similarities". If you want to run only the classes checker, but have
|
||||||
# no Warning level messages displayed, use "--disable=all --enable=classes
|
# no Warning level messages displayed, use "--disable=all --enable=classes
|
||||||
# --disable=W".
|
# --disable=W".
|
||||||
disable=print-statement,
|
disable=raw-checker-failed,
|
||||||
parameter-unpacking,
|
|
||||||
unpacking-in-except,
|
|
||||||
old-raise-syntax,
|
|
||||||
backtick,
|
|
||||||
long-suffix,
|
|
||||||
old-ne-operator,
|
|
||||||
old-octal-literal,
|
|
||||||
import-star-module-level,
|
|
||||||
non-ascii-bytes-literal,
|
|
||||||
raw-checker-failed,
|
|
||||||
bad-inline-option,
|
bad-inline-option,
|
||||||
locally-disabled,
|
locally-disabled,
|
||||||
file-ignored,
|
file-ignored,
|
||||||
@ -78,67 +68,6 @@ disable=print-statement,
|
|||||||
useless-suppression,
|
useless-suppression,
|
||||||
deprecated-pragma,
|
deprecated-pragma,
|
||||||
use-symbolic-message-instead,
|
use-symbolic-message-instead,
|
||||||
apply-builtin,
|
|
||||||
basestring-builtin,
|
|
||||||
buffer-builtin,
|
|
||||||
cmp-builtin,
|
|
||||||
coerce-builtin,
|
|
||||||
execfile-builtin,
|
|
||||||
file-builtin,
|
|
||||||
long-builtin,
|
|
||||||
raw_input-builtin,
|
|
||||||
reduce-builtin,
|
|
||||||
standarderror-builtin,
|
|
||||||
unicode-builtin,
|
|
||||||
xrange-builtin,
|
|
||||||
coerce-method,
|
|
||||||
delslice-method,
|
|
||||||
getslice-method,
|
|
||||||
setslice-method,
|
|
||||||
no-absolute-import,
|
|
||||||
old-division,
|
|
||||||
dict-iter-method,
|
|
||||||
dict-view-method,
|
|
||||||
next-method-called,
|
|
||||||
metaclass-assignment,
|
|
||||||
indexing-exception,
|
|
||||||
raising-string,
|
|
||||||
reload-builtin,
|
|
||||||
oct-method,
|
|
||||||
hex-method,
|
|
||||||
nonzero-method,
|
|
||||||
cmp-method,
|
|
||||||
input-builtin,
|
|
||||||
round-builtin,
|
|
||||||
intern-builtin,
|
|
||||||
unichr-builtin,
|
|
||||||
map-builtin-not-iterating,
|
|
||||||
zip-builtin-not-iterating,
|
|
||||||
range-builtin-not-iterating,
|
|
||||||
filter-builtin-not-iterating,
|
|
||||||
using-cmp-argument,
|
|
||||||
eq-without-hash,
|
|
||||||
div-method,
|
|
||||||
idiv-method,
|
|
||||||
rdiv-method,
|
|
||||||
exception-message-attribute,
|
|
||||||
invalid-str-codec,
|
|
||||||
sys-max-int,
|
|
||||||
bad-python3-import,
|
|
||||||
deprecated-string-function,
|
|
||||||
deprecated-str-translate-call,
|
|
||||||
deprecated-itertools-function,
|
|
||||||
deprecated-types-field,
|
|
||||||
next-method-defined,
|
|
||||||
dict-items-not-iterating,
|
|
||||||
dict-keys-not-iterating,
|
|
||||||
dict-values-not-iterating,
|
|
||||||
deprecated-operator-function,
|
|
||||||
deprecated-urllib-function,
|
|
||||||
xreadlines-attribute,
|
|
||||||
deprecated-sys-function,
|
|
||||||
exception-escape,
|
|
||||||
comprehension-escape,
|
|
||||||
missing-module-docstring,
|
missing-module-docstring,
|
||||||
line-too-long,
|
line-too-long,
|
||||||
no-name-in-module,
|
no-name-in-module,
|
||||||
|
67
CONTRIBUTING.md
Normal file
67
CONTRIBUTING.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Contributing to ahriman
|
||||||
|
|
||||||
|
Welcome to ahriman! The goal of the project is to provide the best user experience to manage Archlinux repositories. In order to follow this we set some limitations for the issue creations and heavily restricted code contribution.
|
||||||
|
|
||||||
|
## Create an issue
|
||||||
|
|
||||||
|
Basically just follow the suggested templates:
|
||||||
|
|
||||||
|
* Bug report requires at least the way to reproduce the issue and behaviour description (expected and actual ones). In order to resolve the bug, the additional questions may be asked, please consider them as lesser evil.
|
||||||
|
* Feature requests basically just require feature description and the purpose why do you want this feature to be implemented. It is required to make sure that the feature you want is going to be implemented in the way you really want it (and to make sure that this feature is not already implemented).
|
||||||
|
* Questions and discussions have free templates, and you are free to ask your question in the way you want.
|
||||||
|
|
||||||
|
## Code contribution
|
||||||
|
|
||||||
|
There are some strict limitation for suggested pull requests:
|
||||||
|
|
||||||
|
* `autopep8`, `bandit`, `pylint`, `mypy` must pass.
|
||||||
|
* Test coverage must remain 100%.
|
||||||
|
|
||||||
|
### Code formatting
|
||||||
|
|
||||||
|
In order to resolve all difficult cases the `autopep8` is used. You can perform formatting at any time by running `make check` or running `autopep8` command directly.
|
||||||
|
|
||||||
|
### Code style
|
||||||
|
|
||||||
|
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.
|
||||||
|
* 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.
|
||||||
|
* The code must follow the exception safety, unless it is explicitly asked by end user. It means that most exceptions must be handled and printed to log, no other actions must be done (e.g. raising another exception).
|
||||||
|
* For the external command `ahriman.core.util.check_output` function must be used.
|
||||||
|
* Every temporary file/directory must be removed at the end of processing, no matter what. The ``tempfile`` module provides good ways to do it.
|
||||||
|
* Import order must be the following:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# optional imports from future module
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# Module import for those which are installed into environment (no matter standard library or not)...
|
||||||
|
import os
|
||||||
|
# ...one per line...
|
||||||
|
import pyalpm
|
||||||
|
# ...in alphabetical order
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Blank line between
|
||||||
|
# ``from module import submodule`` import
|
||||||
|
from pathlib import Path
|
||||||
|
# ...again in alphabet order. It is possible to do several imports, but make sure that they are also in alphabetical order.
|
||||||
|
from pyalpm import Handle, Package
|
||||||
|
|
||||||
|
# Blank line again and package imports
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
* One file should define only one class, exception is class satellites in case if file length remain less than 200 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.
|
||||||
|
* No global variable allowed outside of `ahriman.version` module.
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
### Other checks
|
||||||
|
|
||||||
|
The projects also uses typing checks (provided by `mypy`) and some linter checks provided by `pylint` and `bandit`. Those checks must be passed successfully for any open pull requests.
|
30
Dockerfile
30
Dockerfile
@ -1,4 +1,4 @@
|
|||||||
FROM archlinux:base-devel
|
FROM archlinux:base
|
||||||
|
|
||||||
# image configuration
|
# image configuration
|
||||||
ENV AHRIMAN_ARCHITECTURE="x86_64"
|
ENV AHRIMAN_ARCHITECTURE="x86_64"
|
||||||
@ -13,24 +13,22 @@ ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman"
|
|||||||
ENV AHRIMAN_USER="ahriman"
|
ENV AHRIMAN_USER="ahriman"
|
||||||
|
|
||||||
# install environment
|
# install environment
|
||||||
## install git which is required for AUR interaction and go for yay
|
## install minimal required packages
|
||||||
RUN pacman --noconfirm -Syu git go
|
RUN pacman --noconfirm -Syu binutils fakeroot git make sudo
|
||||||
## create build user
|
## create build user
|
||||||
RUN useradd -m -d /home/build -s /usr/bin/nologin build && \
|
RUN useradd -m -d /home/build -s /usr/bin/nologin build && \
|
||||||
echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build
|
echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build
|
||||||
## install AUR helper
|
COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
|
||||||
RUN YAY_DIR="$(runuser -u build -- mktemp -d)" && \
|
|
||||||
git clone https://aur.archlinux.org/yay.git "$YAY_DIR" && \
|
|
||||||
cd "$YAY_DIR" && \
|
|
||||||
runuser -u build -- makepkg --noconfirm --install && \
|
|
||||||
cd - && rm -r "$YAY_DIR"
|
|
||||||
## install package dependencies
|
## install package dependencies
|
||||||
RUN runuser -u build -- yay --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-requests python-srcinfo && \
|
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
|
||||||
runuser -u build -- yay --noconfirm -Sy python-build python-installer python-wheel && \
|
RUN pacman --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-requests python-setuptools python-srcinfo && \
|
||||||
runuser -u build -- yay --noconfirm -Sy breezy darcs mercurial python-aioauth-client python-aiohttp \
|
pacman --noconfirm -Sy python-build python-installer python-wheel && \
|
||||||
python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security \
|
pacman --noconfirm -Sy breezy mercurial python-aiohttp python-boto3 python-cryptography python-jinja rsync subversion && \
|
||||||
python-aiohttp-session python-boto3 python-cryptography python-jinja \
|
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-jinja2 python-aiohttp-debugtoolbar \
|
||||||
rsync subversion
|
python-aiohttp-session python-aiohttp-security
|
||||||
|
|
||||||
|
# cleanup unused
|
||||||
|
RUN find "/var/cache/pacman/pkg" -type f -delete
|
||||||
|
|
||||||
# install ahriman
|
# install ahriman
|
||||||
## copy tree
|
## copy tree
|
||||||
@ -41,7 +39,7 @@ RUN cd "/home/build/ahriman" && \
|
|||||||
cp ./*-src.tar.xz "package/archlinux" && \
|
cp ./*-src.tar.xz "package/archlinux" && \
|
||||||
cd "package/archlinux" && \
|
cd "package/archlinux" && \
|
||||||
runuser -u build -- makepkg --noconfirm --install --skipchecksums && \
|
runuser -u build -- makepkg --noconfirm --install --skipchecksums && \
|
||||||
cd - && rm -r "/home/build/ahriman"
|
cd / && rm -r "/home/build/ahriman"
|
||||||
|
|
||||||
VOLUME ["/var/lib/ahriman"]
|
VOLUME ["/var/lib/ahriman"]
|
||||||
|
|
||||||
|
16
Makefile
16
Makefile
@ -1,9 +1,9 @@
|
|||||||
.PHONY: archive archive_directory archlinux check clean directory push spec spec-html tests version
|
.PHONY: archive archlinux check clean directory html push specification tests version
|
||||||
.DEFAULT_GOAL := archlinux
|
.DEFAULT_GOAL := archlinux
|
||||||
|
|
||||||
PROJECT := ahriman
|
PROJECT := ahriman
|
||||||
|
|
||||||
FILES := AUTHORS COPYING README.md docs package src setup.py tox.ini web.png
|
FILES := AUTHORS CONTRIBUTING.md COPYING README.md docs package src setup.py tox.ini web.png
|
||||||
TARGET_FILES := $(addprefix $(PROJECT)/, $(FILES))
|
TARGET_FILES := $(addprefix $(PROJECT)/, $(FILES))
|
||||||
IGNORE_FILES := package/archlinux src/.mypy_cache
|
IGNORE_FILES := package/archlinux src/.mypy_cache
|
||||||
|
|
||||||
@ -33,22 +33,22 @@ clean:
|
|||||||
directory: clean
|
directory: clean
|
||||||
mkdir "$(PROJECT)"
|
mkdir "$(PROJECT)"
|
||||||
|
|
||||||
push: spec archlinux
|
html: specification
|
||||||
|
rm -rf docs/html
|
||||||
|
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
|
||||||
git commit -m "Release $(VERSION)"
|
git commit -m "Release $(VERSION)"
|
||||||
git tag "$(VERSION)"
|
git tag "$(VERSION)"
|
||||||
git push
|
git push
|
||||||
git push --tags
|
git push --tags
|
||||||
|
|
||||||
spec:
|
specification:
|
||||||
# make sure that old files are removed
|
# make sure that old files are removed
|
||||||
find docs -type f -name "$(PROJECT)*.rst" -delete
|
find docs -type f -name "$(PROJECT)*.rst" -delete
|
||||||
tox -e docs
|
tox -e docs
|
||||||
|
|
||||||
spec-html: spec
|
|
||||||
rm -rf docs/html
|
|
||||||
tox -e docs-html
|
|
||||||
|
|
||||||
tests: clean
|
tests: clean
|
||||||
tox -e tests
|
tox -e tests
|
||||||
|
|
||||||
|
19
README.md
19
README.md
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/arcan1s/ahriman/actions/workflows/run-tests.yml)
|
[](https://github.com/arcan1s/ahriman/actions/workflows/run-tests.yml)
|
||||||
[](https://github.com/arcan1s/ahriman/actions/workflows/run-setup.yml)
|
[](https://github.com/arcan1s/ahriman/actions/workflows/run-setup.yml)
|
||||||
[](https://github.com/arcan1s/ahriman/actions/workflows/docker-image.yml)
|
[](https://hub.docker.com/r/arcan1s/ahriman)
|
||||||
[](https://www.codefactor.io/repository/github/arcan1s/ahriman)
|
[](https://www.codefactor.io/repository/github/arcan1s/ahriman)
|
||||||
[](https://ahriman.readthedocs.io/?badge=latest)
|
[](https://ahriman.readthedocs.io/?badge=latest)
|
||||||
|
|
||||||
@ -10,23 +10,26 @@ Wrapper for managing custom repository inspired by [repo-scripts](https://github
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Install-configure-forget manager for own repository.
|
* Install-configure-forget manager for the very own repository.
|
||||||
* Multi-architecture support.
|
* Multi-architecture support.
|
||||||
* VCS packages support.
|
|
||||||
* Sign support with gpg (repository, package, per package settings).
|
|
||||||
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram) and even ability to write own extensions.
|
|
||||||
* Dependency manager.
|
* Dependency manager.
|
||||||
|
* VCS packages support.
|
||||||
|
* Official repository support.
|
||||||
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
||||||
|
* Sign support with gpg (repository, package, per package settings).
|
||||||
|
* 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:
|
* Repository status interface with optional authorization and control options:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Installation and run
|
## Installation and run
|
||||||
|
|
||||||
For installation details please refer to the [documentation](docs/setup.rst). For command help, `--help` subcommand must be used. Subcommands have own help message as well. The package also provides a [man page](docs/ahriman.1).
|
For installation details kindly refer to the [documentation](https://ahriman.readthedocs.io/en/latest/setup.html). For application commands it is possible to get information by using `--help`/`help` command or by using man page ([web version](https://ahriman.readthedocs.io/en/latest/command-line.html)).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Every available option is described in the [documentation](docs/configuration.rst).
|
Every available option is described in the [documentation](https://ahriman.readthedocs.io/en/latest/configuration.html).
|
||||||
|
|
||||||
## [FAQ](docs/faq.rst)
|
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)
|
||||||
|
@ -33,7 +33,7 @@ fi
|
|||||||
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-setup "${AHRIMAN_SETUP_ARGS[@]}"
|
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-setup "${AHRIMAN_SETUP_ARGS[@]}"
|
||||||
|
|
||||||
# refresh database
|
# refresh database
|
||||||
runuser -u build -- yay --noconfirm -Syy &> /dev/null
|
pacman -Syy &> /dev/null
|
||||||
# create machine-id which is required by build tools
|
# create machine-id which is required by build tools
|
||||||
systemd-machine-id-setup &> /dev/null
|
systemd-machine-id-setup &> /dev/null
|
||||||
|
|
||||||
|
12
docker/install-aur-package.sh
Executable file
12
docker/install-aur-package.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
for PACKAGE in "$@"; do
|
||||||
|
BUILD_DIR="$(mktemp -d)"
|
||||||
|
git clone https://aur.archlinux.org/"$PACKAGE".git "$BUILD_DIR"
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
makepkg --noconfirm --install --rmdeps --syncdeps
|
||||||
|
cd /
|
||||||
|
rm -r "$BUILD_DIR"
|
||||||
|
done
|
@ -37,7 +37,6 @@ And the ``repository`` instance can be used to perform repository maintenance
|
|||||||
built_packages = repository.packages_built()
|
built_packages = repository.packages_built()
|
||||||
update_result = repository.process_update(built_packages)
|
update_result = repository.process_update(built_packages)
|
||||||
|
|
||||||
repository.process_report(None, update_result)
|
repository.process_triggers(update_result)
|
||||||
repository.process_sync(None, update_result.success)
|
|
||||||
|
|
||||||
For the more info please refer to the classes documentation.
|
For the more info please refer to the classes documentation.
|
||||||
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 517 KiB After Width: | Height: | Size: 537 KiB |
@ -3,9 +3,9 @@
|
|||||||
ahriman
|
ahriman
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B ahriman
|
.B ahriman
|
||||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-triggers,repo-update,update,user-add,user-list,user-remove,web} ...
|
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-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} ...
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
ArcH Linux ReposItory MANager
|
ArcH linux ReposItory MANager
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
@ -37,7 +37,7 @@ force disable any logging
|
|||||||
allow to run ahriman as non\-ahriman user. Some actions might be unavailable
|
allow to run ahriman as non\-ahriman user. Some actions might be unavailable
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-v\fR, \fB\-\-version\fR
|
\fB\-V\fR, \fB\-\-version\fR
|
||||||
show program's version number and exit
|
show program's version number and exit
|
||||||
|
|
||||||
.SH
|
.SH
|
||||||
@ -97,6 +97,9 @@ rebuild repository
|
|||||||
\fBahriman\fR \fI\,repo-remove-unknown\/\fR
|
\fBahriman\fR \fI\,repo-remove-unknown\/\fR
|
||||||
remove unknown packages
|
remove unknown packages
|
||||||
.TP
|
.TP
|
||||||
|
\fBahriman\fR \fI\,repo-report\/\fR
|
||||||
|
generate report
|
||||||
|
.TP
|
||||||
\fBahriman\fR \fI\,repo-restore\/\fR
|
\fBahriman\fR \fI\,repo-restore\/\fR
|
||||||
restore repository data
|
restore repository data
|
||||||
.TP
|
.TP
|
||||||
@ -109,12 +112,18 @@ sign packages
|
|||||||
\fBahriman\fR \fI\,repo-status-update\/\fR
|
\fBahriman\fR \fI\,repo-status-update\/\fR
|
||||||
update repository status
|
update repository status
|
||||||
.TP
|
.TP
|
||||||
|
\fBahriman\fR \fI\,repo-sync\/\fR
|
||||||
|
sync repository
|
||||||
|
.TP
|
||||||
\fBahriman\fR \fI\,repo-triggers\/\fR
|
\fBahriman\fR \fI\,repo-triggers\/\fR
|
||||||
run triggers
|
run triggers
|
||||||
.TP
|
.TP
|
||||||
\fBahriman\fR \fI\,repo-update\/\fR
|
\fBahriman\fR \fI\,repo-update\/\fR
|
||||||
update packages
|
update packages
|
||||||
.TP
|
.TP
|
||||||
|
\fBahriman\fR \fI\,shell\/\fR
|
||||||
|
envoke python shell
|
||||||
|
.TP
|
||||||
\fBahriman\fR \fI\,user-add\/\fR
|
\fBahriman\fR \fI\,user-add\/\fR
|
||||||
create or update user
|
create or update user
|
||||||
.TP
|
.TP
|
||||||
@ -124,6 +133,9 @@ user known users and their access
|
|||||||
\fBahriman\fR \fI\,user-remove\/\fR
|
\fBahriman\fR \fI\,user-remove\/\fR
|
||||||
remove user
|
remove user
|
||||||
.TP
|
.TP
|
||||||
|
\fBahriman\fR \fI\,version\/\fR
|
||||||
|
application version
|
||||||
|
.TP
|
||||||
\fBahriman\fR \fI\,web\/\fR
|
\fBahriman\fR \fI\,web\/\fR
|
||||||
web server
|
web server
|
||||||
.SH COMMAND \fI\,'ahriman aur-search'\/\fR
|
.SH COMMAND \fI\,'ahriman aur-search'\/\fR
|
||||||
@ -186,8 +198,7 @@ PGP key to import from public server
|
|||||||
key server for key import
|
key server for key import
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman package-add'\/\fR
|
.SH COMMAND \fI\,'ahriman package-add'\/\fR
|
||||||
usage: ahriman package-add [-h] [-e] [-n]
|
usage: ahriman package-add [-h] [-e] [-n] [-s {auto,archive,aur,directory,local,remote,repository}]
|
||||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}]
|
|
||||||
[--without-dependencies]
|
[--without-dependencies]
|
||||||
package [package ...]
|
package [package ...]
|
||||||
|
|
||||||
@ -207,7 +218,7 @@ return non\-zero exit status if result is empty
|
|||||||
run update function after
|
run update function after
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-s\fR \fI\,{PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}\/\fR, \fB\-\-source\fR \fI\,{PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}\/\fR
|
\fB\-s\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR, \fB\-\-source\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR
|
||||||
explicitly specify the package source for this command
|
explicitly specify the package source for this command
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -224,9 +235,7 @@ remove package from the repository
|
|||||||
package name or base
|
package name or base
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman package-status'\/\fR
|
.SH COMMAND \fI\,'ahriman package-status'\/\fR
|
||||||
usage: ahriman package-status [-h] [--ahriman] [-e] [-i]
|
usage: ahriman package-status [-h] [--ahriman] [-e] [-i] [-s {unknown,pending,building,failed,success}] [package ...]
|
||||||
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
|
|
||||||
[package ...]
|
|
||||||
|
|
||||||
request status of the package
|
request status of the package
|
||||||
|
|
||||||
@ -248,7 +257,7 @@ return non\-zero exit status if result is empty
|
|||||||
show additional package information
|
show additional package information
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-s\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR, \fB\-\-status\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR
|
\fB\-s\fR \fI\,{unknown,pending,building,failed,success}\/\fR, \fB\-\-status\fR \fI\,{unknown,pending,building,failed,success}\/\fR
|
||||||
filter packages by status
|
filter packages by status
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman package-status-remove'\/\fR
|
.SH COMMAND \fI\,'ahriman package-status-remove'\/\fR
|
||||||
@ -261,9 +270,7 @@ remove the package from the status page
|
|||||||
remove specified packages
|
remove specified packages
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman package-status-update'\/\fR
|
.SH COMMAND \fI\,'ahriman package-status-update'\/\fR
|
||||||
usage: ahriman package-status-update [-h]
|
usage: ahriman package-status-update [-h] [-s {unknown,pending,building,failed,success}] [package ...]
|
||||||
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
|
|
||||||
[package ...]
|
|
||||||
|
|
||||||
update package status on the status page
|
update package status on the status page
|
||||||
|
|
||||||
@ -273,7 +280,7 @@ set status for specified packages. If no packages supplied, service status will
|
|||||||
|
|
||||||
.SH OPTIONS \fI\,'ahriman package-status-update'\/\fR
|
.SH OPTIONS \fI\,'ahriman package-status-update'\/\fR
|
||||||
.TP
|
.TP
|
||||||
\fB\-s\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR, \fB\-\-status\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR
|
\fB\-s\fR \fI\,{unknown,pending,building,failed,success}\/\fR, \fB\-\-status\fR \fI\,{unknown,pending,building,failed,success}\/\fR
|
||||||
new status
|
new status
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman patch-add'\/\fR
|
.SH COMMAND \fI\,'ahriman patch-add'\/\fR
|
||||||
@ -405,6 +412,11 @@ just perform check for packages without removal
|
|||||||
\fB\-i\fR, \fB\-\-info\fR
|
\fB\-i\fR, \fB\-\-info\fR
|
||||||
show additional package information
|
show additional package information
|
||||||
|
|
||||||
|
.SH COMMAND \fI\,'ahriman repo-report'\/\fR
|
||||||
|
usage: ahriman repo-report [-h]
|
||||||
|
|
||||||
|
generate repository report according to current settings
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman repo-restore'\/\fR
|
.SH COMMAND \fI\,'ahriman repo-restore'\/\fR
|
||||||
usage: ahriman repo-restore [-h] [-o OUTPUT] path
|
usage: ahriman repo-restore [-h] [-o OUTPUT] path
|
||||||
|
|
||||||
@ -422,8 +434,7 @@ root path of the extracted files
|
|||||||
.SH COMMAND \fI\,'ahriman repo-setup'\/\fR
|
.SH COMMAND \fI\,'ahriman repo-setup'\/\fR
|
||||||
usage: ahriman repo-setup [-h] [--build-as-user BUILD_AS_USER] [--build-command BUILD_COMMAND]
|
usage: ahriman repo-setup [-h] [--build-as-user BUILD_AS_USER] [--build-command BUILD_COMMAND]
|
||||||
[--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository
|
[--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository
|
||||||
REPOSITORY [--sign-key SIGN_KEY]
|
REPOSITORY [--sign-key SIGN_KEY] [--sign-target {disabled,pacakges,repository}]
|
||||||
[--sign-target {SignSettings.Disabled,SignSettings.Packages,SignSettings.Repository}]
|
|
||||||
[--web-port WEB_PORT]
|
[--web-port WEB_PORT]
|
||||||
|
|
||||||
create initial service configuration, requires root
|
create initial service configuration, requires root
|
||||||
@ -458,7 +469,7 @@ repository name
|
|||||||
sign key id
|
sign key id
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-sign\-target\fR \fI\,{SignSettings.Disabled,SignSettings.Packages,SignSettings.Repository}\/\fR
|
\fB\-\-sign\-target\fR \fI\,{disabled,pacakges,repository}\/\fR
|
||||||
sign options
|
sign options
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -475,21 +486,29 @@ usage: ahriman repo-sign [-h] [package ...]
|
|||||||
sign only specified packages
|
sign only specified packages
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman repo-status-update'\/\fR
|
.SH COMMAND \fI\,'ahriman repo-status-update'\/\fR
|
||||||
usage: ahriman repo-status-update [-h]
|
usage: ahriman repo-status-update [-h] [-s {unknown,pending,building,failed,success}]
|
||||||
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
|
|
||||||
|
|
||||||
update repository status on the status page
|
update repository status on the status page
|
||||||
|
|
||||||
.SH OPTIONS \fI\,'ahriman repo-status-update'\/\fR
|
.SH OPTIONS \fI\,'ahriman repo-status-update'\/\fR
|
||||||
.TP
|
.TP
|
||||||
\fB\-s\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR, \fB\-\-status\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR
|
\fB\-s\fR \fI\,{unknown,pending,building,failed,success}\/\fR, \fB\-\-status\fR \fI\,{unknown,pending,building,failed,success}\/\fR
|
||||||
new status
|
new status
|
||||||
|
|
||||||
|
.SH COMMAND \fI\,'ahriman repo-sync'\/\fR
|
||||||
|
usage: ahriman repo-sync [-h]
|
||||||
|
|
||||||
|
sync repository files to remote server according to current settings
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman repo-triggers'\/\fR
|
.SH COMMAND \fI\,'ahriman repo-triggers'\/\fR
|
||||||
usage: ahriman repo-triggers [-h]
|
usage: ahriman repo-triggers [-h] [trigger ...]
|
||||||
|
|
||||||
run triggers on empty build result as configured by settings
|
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
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman repo-update'\/\fR
|
.SH COMMAND \fI\,'ahriman repo-update'\/\fR
|
||||||
usage: ahriman repo-update [-h] [--dry-run] [-e] [--no-aur] [--no-local] [--no-manual] [--no-vcs] [package ...]
|
usage: ahriman repo-update [-h] [--dry-run] [-e] [--no-aur] [--no-local] [--no-manual] [--no-vcs] [package ...]
|
||||||
|
|
||||||
@ -524,9 +543,13 @@ do not include manual updates
|
|||||||
\fB\-\-no\-vcs\fR
|
\fB\-\-no\-vcs\fR
|
||||||
do not check VCS packages
|
do not check VCS packages
|
||||||
|
|
||||||
|
.SH COMMAND \fI\,'ahriman shell'\/\fR
|
||||||
|
usage: ahriman shell [-h]
|
||||||
|
|
||||||
|
drop into python shell while having created application
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman user-add'\/\fR
|
.SH COMMAND \fI\,'ahriman user-add'\/\fR
|
||||||
usage: ahriman user-add [-h] [--as-service] [-p PASSWORD] [-r {UserAccess.Safe,UserAccess.Read,UserAccess.Write}] [-s]
|
usage: ahriman user-add [-h] [--as-service] [-p PASSWORD] [-r {unauthorized,read,reporter,full}] [-s] username
|
||||||
username
|
|
||||||
|
|
||||||
update user for web services with the given password and role. In case if password was not entered it will be asked interactively
|
update user for web services with the given password and role. In case if password was not entered it will be asked interactively
|
||||||
|
|
||||||
@ -545,7 +568,7 @@ user password. Blank password will be treated as empty password, which is in par
|
|||||||
authorization type.
|
authorization type.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-r\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR, \fB\-\-role\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR
|
\fB\-r\fR \fI\,{unauthorized,read,reporter,full}\/\fR, \fB\-\-role\fR \fI\,{unauthorized,read,reporter,full}\/\fR
|
||||||
user access level
|
user access level
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -553,7 +576,7 @@ user access level
|
|||||||
set file permissions to user\-only
|
set file permissions to user\-only
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman user-list'\/\fR
|
.SH COMMAND \fI\,'ahriman user-list'\/\fR
|
||||||
usage: ahriman user-list [-h] [-e] [-r {UserAccess.Safe,UserAccess.Read,UserAccess.Write}] [username]
|
usage: ahriman user-list [-h] [-e] [-r {unauthorized,read,reporter,full}] [username]
|
||||||
|
|
||||||
list users from the user mapping and their roles
|
list users from the user mapping and their roles
|
||||||
|
|
||||||
@ -567,7 +590,7 @@ filter users by username
|
|||||||
return non\-zero exit status if result is empty
|
return non\-zero exit status if result is empty
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-r\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR, \fB\-\-role\fR \fI\,{UserAccess.Safe,UserAccess.Read,UserAccess.Write}\/\fR
|
\fB\-r\fR \fI\,{unauthorized,read,reporter,full}\/\fR, \fB\-\-role\fR \fI\,{unauthorized,read,reporter,full}\/\fR
|
||||||
filter users by role
|
filter users by role
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman user-remove'\/\fR
|
.SH COMMAND \fI\,'ahriman user-remove'\/\fR
|
||||||
@ -584,6 +607,11 @@ username for web service
|
|||||||
\fB\-s\fR, \fB\-\-secure\fR
|
\fB\-s\fR, \fB\-\-secure\fR
|
||||||
set file permissions to user\-only
|
set file permissions to user\-only
|
||||||
|
|
||||||
|
.SH COMMAND \fI\,'ahriman version'\/\fR
|
||||||
|
usage: ahriman version [-h]
|
||||||
|
|
||||||
|
print application and its dependencies versions
|
||||||
|
|
||||||
.SH COMMAND \fI\,'ahriman web'\/\fR
|
.SH COMMAND \fI\,'ahriman web'\/\fR
|
||||||
usage: ahriman web [-h]
|
usage: ahriman web [-h]
|
||||||
|
|
||||||
|
@ -116,6 +116,14 @@ ahriman.application.handlers.setup module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.application.handlers.shell module
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.application.handlers.shell
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.application.handlers.sign module
|
ahriman.application.handlers.sign module
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
@ -172,6 +180,14 @@ ahriman.application.handlers.users module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.application.handlers.versions module
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.application.handlers.versions
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.application.handlers.web module
|
ahriman.application.handlers.web module
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
|
@ -20,6 +20,14 @@ ahriman.core.database.migrations.m001\_package\_source module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.core.database.migrations.m002\_user\_access module
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.database.migrations.m002_user_access
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -76,6 +76,14 @@ ahriman.core.formatters.user\_printer module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.core.formatters.version\_printer module
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.formatters.version_printer
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -38,6 +38,14 @@ ahriman.core.exceptions module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.core.lazy\_logging module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.lazy_logging
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.core.spawn module
|
ahriman.core.spawn module
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -92,6 +92,14 @@ ahriman.models.package\_source module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.models.pkgbuild\_patch module
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.models.pkgbuild_patch
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.models.property module
|
ahriman.models.property module
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
@ -4,14 +4,6 @@ ahriman.web.views.status package
|
|||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
ahriman.web.views.status.ahriman module
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.views.status.ahriman
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.views.status.package module
|
ahriman.web.views.status.package module
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
|
@ -19,27 +19,28 @@ Full dependency diagram:
|
|||||||
``ahriman.application`` package
|
``ahriman.application`` package
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This package contains application (aka executable) related classes and everything for that. It also contains package called ``ahriman.application.handlers`` in which all available subcommands are described as separated classes derived from base ``ahriman.application.handlers.handler.Handler`` class.
|
This package contains application (aka executable) related classes and everything for that. It also contains package called ``ahriman.application.handlers`` in which all available subcommands are described as separated classes derived from base ``ahriman.application.handlers.Handler`` class.
|
||||||
|
|
||||||
``ahriman.application.application.application.Application`` (god class) is used for any interaction from parsers with repository, web etc. It is divided into multiple traits by functions (package related and repository related) in the same package.
|
``ahriman.application.application.Application`` (god class) is used for any interaction from parsers with repository. It is divided into multiple traits by functions (package related and repository related) in the same package.
|
||||||
|
|
||||||
``ahriman.application.ahriman`` contains only command line parses and executes specified ``Handler`` on success, ``ahriman.application.lock.Lock`` is additional class which provides file-based lock and also performs some common checks.
|
``ahriman.application.ahriman`` contains only command line parses and executes specified ``Handler`` on success, ``ahriman.application.lock.Lock`` is additional class which provides file-based lock and also performs some common checks.
|
||||||
|
|
||||||
``ahriman.core`` package
|
``ahriman.core`` package
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This package contains everything which is required for any time of application run and separated to several packages:
|
This package contains everything which is required for any time of application run and 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.alpm`` package controls pacman related functions. It provides wrappers for ``pyalpm`` library and safe calls for repository tools (``repo-add`` and ``repo-remove``). Also this package contains ``ahriman.core.alpm.remote`` package which provides wrapper for remote sources (e.g. AUR RPC and official repositories RPC).
|
||||||
* ``ahriman.core.auth`` package provides classes for authorization methods used by web mostly. Base class is ``ahriman.core.auth.auth.Auth`` which must be called by ``load`` method.
|
* ``ahriman.core.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.
|
||||||
* ``ahriman.core.build_tools`` is a package which provides wrapper for ``devtools`` commands.
|
* ``ahriman.core.build_tools`` is a package which provides wrapper for ``devtools`` commands.
|
||||||
* ``ahriman.core.database`` is everything including data and schema migrations for database.
|
* ``ahriman.core.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.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
||||||
* ``ahriman.core.report`` is a package with reporting classes. Usually it must be called by ``ahriman.core.report.report.Report.load`` method.
|
* ``ahriman.core.report`` is a package with reporting classes. Usually it must be called by ``ahriman.core.report.Report.load`` method.
|
||||||
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.repository.Repository`` class) implementation.
|
* ``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).
|
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
|
||||||
* ``ahriman.core.status`` contains helpers and watcher class which are required for web application. Reporter must be initialized by using ``ahriman.core.status.client.Client.load`` method.
|
* ``ahriman.core.status`` contains helpers and watcher class which are required for web application. Reporter must be initialized by using ``ahriman.core.status.client.Client.load`` method.
|
||||||
* ``ahriman.core.upload`` package provides sync feature, must be called by ``ahriman.core.upload.upload.Upload.load`` method.
|
* ``ahriman.core.triggers`` package contains base trigger classes. Classes from this package must be imported in order to implement user extensions. In fact, ``ahriman.core.report`` and ``ahriman.core.upload`` use this package.
|
||||||
|
* ``ahriman.core.upload`` package provides sync feature, must be called by ``ahriman.core.upload.Upload.load`` method.
|
||||||
|
|
||||||
This package also provides some generic functions and classes which may be used by other packages:
|
This package also provides some generic functions and classes which may be used by other packages:
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ Application run
|
|||||||
|
|
||||||
* Parse command line arguments, find command and related handler which is set by parser.
|
* Parse command line arguments, find command and related handler which is set by parser.
|
||||||
* Call ``Handler.execute`` method.
|
* Call ``Handler.execute`` method.
|
||||||
* Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or process in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Web``) which should be able to spawn child process in daemon mode (it is impossible to do for daemonic processes).
|
* Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or process in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Web``) which should be able to spawn child process in daemon mode (it is impossible to do from daemonic processes).
|
||||||
* In each child process call lock functions.
|
* In each child process call lock functions.
|
||||||
* After success checks pass control to ``Handler.run`` method defined by specific handler class.
|
* After success checks pass control to ``Handler.run`` method defined by specific handler class.
|
||||||
* Return result (success or failure) of each subprocess and exit from application.
|
* Return result (success or failure) of each subprocess and exit from application.
|
||||||
@ -86,14 +87,14 @@ The service uses SQLite database in order to store some internal info.
|
|||||||
Database instance
|
Database instance
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
All methods related to specific part of database (basically operations per table) are split into different traits located inside ``ahriman.core.database.operations`` package. The base trait ``ahriman.core.database.operations.operations.Operations`` also provides generic methods for database access (e.g. row converters and transactional support).
|
All methods related to specific part of database (basically operations per table) are split into different traits located inside ``ahriman.core.database.operations`` package. The base trait ``ahriman.core.database.operations.Operations`` also provides generic methods for database access (e.g. row converters and transactional support).
|
||||||
|
|
||||||
The ``ahriman.core.database.sqlite.SQLite`` class itself derives from all of these traits and implements methods for initialization, including migrations.
|
The ``ahriman.core.database.SQLite`` class itself derives from all of these traits and implements methods for initialization, including migrations.
|
||||||
|
|
||||||
Schema and data migrations
|
Schema and data migrations
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The schema migration are applied according to current ``pragma user_info`` values, located at ``ahriman.core.database.migrations`` package and named as ``m000_migration_name.py`` (the preceding ``m`` is required in order to import migration content for tests). Additional class ``ahriman.core.database.migrations.Migrations`` reads all migrations autmatically and applies them in alphabetical order.
|
The schema migration are applied according to current ``pragma user_info`` values, located at ``ahriman.core.database.migrations`` package and named as ``m000_migration_name.py`` (the preceding ``m`` is required in order to import migration content for tests). Additional class ``ahriman.core.database.migrations.Migrations`` reads all migrations automatically and applies them in alphabetical order.
|
||||||
|
|
||||||
There are also data migrations which are located at ``ahriman.core.database.data`` package and move data from old-style (e.g. json files in filesystem, directory trees, etc) to the database. They are also part of migration and (unlike schema migrations) are applied only at specific version breakpoints (e.g. if ``user_version`` is more than 0 no initial migration will be applied).
|
There are also data migrations which are located at ``ahriman.core.database.data`` package and move data from old-style (e.g. json files in filesystem, directory trees, etc) to the database. They are also part of migration and (unlike schema migrations) are applied only at specific version breakpoints (e.g. if ``user_version`` is more than 0 no initial migration will be applied).
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ Idea is to copy package to the directory from which it will be handled at the ne
|
|||||||
|
|
||||||
* If supplied argument is file then application moves the file to the directory with built packages. Same rule applies for directory, but in this case it copies every package-like file from the specified directory.
|
* If supplied argument is file then application moves the file to the directory with built packages. Same rule applies for directory, but in this case it copies every package-like file from the specified directory.
|
||||||
* If supplied argument is directory and there is ``PKGBUILD`` file there it will be treated as local package. In this case it will queue this package to build and copy source files (``PKGBUILD`` and ``.SRCINFO``) to caches.
|
* If supplied argument is directory and there is ``PKGBUILD`` file there it will be treated as local package. In this case it will queue this package to build and copy source files (``PKGBUILD`` and ``.SRCINFO``) to caches.
|
||||||
* If supplied argument iis not file then application tries to lookup for the specified name in AUR and clones it into the directory with manual updates. This scenario can also handle package dependencies which are missing in repositories.
|
* If supplied argument is not file then application tries to lookup for the specified name in AUR and clones it into the directory with manual updates. This scenario can also handle package dependencies which are missing in repositories.
|
||||||
|
|
||||||
This logic can be overwritten by specifying the ``source`` parameter, which is partially useful if you would like to add package from AUR, but there is local directory cloned from AUR.
|
This logic can be overwritten by specifying the ``source`` parameter, which is partially useful if you would like to add package from AUR, but there is local directory cloned from AUR.
|
||||||
|
|
||||||
@ -154,6 +155,13 @@ Configuration
|
|||||||
|
|
||||||
``ahriman.core.configuration.Configuration`` class provides some additional methods (e.g. ``getpath`` and ``getlist``) and also combines multiple files into single configuration dictionary using architecture overrides. It is the recommended way to deal with settings.
|
``ahriman.core.configuration.Configuration`` class provides some additional methods (e.g. ``getpath`` and ``getlist``) and also combines multiple files into single configuration dictionary using architecture overrides. It is the recommended way to deal with settings.
|
||||||
|
|
||||||
|
Enumerations
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
All enumerations are derived from ``str`` and ``enum.Enum``. Integer enumerations are not allowed, because most of operations require conversions from string variable. Derivation from string class is required to make json conversions implicitly (e.g. during calling ``json.dumps`` methods).
|
||||||
|
|
||||||
|
In addition, some enumerations provide ``from_option`` class methods in order to allow some flexibility while reading configuration options.
|
||||||
|
|
||||||
Utils
|
Utils
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
@ -262,7 +270,7 @@ Requests and scopes
|
|||||||
|
|
||||||
Service provides optional authorization which can be turned on in settings. In order to control user access there are two levels of authorization - read-only (only GET-like requests) and write (anything) which are provided by each web view directly.
|
Service provides optional authorization which can be turned on in settings. In order to control user access there are two levels of authorization - read-only (only GET-like requests) and write (anything) which are provided by each web view directly.
|
||||||
|
|
||||||
If this feature is configured any request will be prohibited without authentication. In addition, configuration flag ``auth.safe_build_status`` can be used in order to allow seeing main page without authorization.
|
If this feature is configured any request will be prohibited without authentication. In addition, configuration flag ``auth.allow_read_only`` can be used in order to allow read-only operations - reading index page and packages - without authorization.
|
||||||
|
|
||||||
For authenticated users it uses encrypted session cookies to store tokens; encryption key is generated each time at the start of the application. It also stores expiration time of the session inside.
|
For authenticated users it uses encrypted session cookies to store tokens; encryption key is generated each time at the start of the application. It also stores expiration time of the session inside.
|
||||||
|
|
||||||
|
@ -104,5 +104,4 @@ autodoc_member_order = "groupwise"
|
|||||||
|
|
||||||
autodoc_default_options = {
|
autodoc_default_options = {
|
||||||
"no-undoc-members": True,
|
"no-undoc-members": True,
|
||||||
"special-members": "__init__",
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Configuration
|
Configuration
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Some groups can be specified for each architecture separately. E.g. if there are ``build`` and ``build:x86_64`` groups it will use the option from ``build:x86_64`` for the ``x86_64`` architecture and ``build`` for any other (architecture specific group has higher priority). In case if both groups are presented, architecture specific options will be merged into global ones overriding them.
|
Some groups can be specified for each architecture separately. E.g. if there are ``build`` and ``build:x86_64`` groups it will use an option from ``build:x86_64`` for the ``x86_64`` architecture and ``build`` for any other (architecture specific group has higher priority). In case if both groups are presented, architecture specific options will be merged into global ones overriding them.
|
||||||
|
|
||||||
There are two variable types which have been added to default ones, they are paths and lists. List values will be read in the same way as shell does:
|
There are two variable types which have been added to default ones, they are paths and lists. List values will be read in the same way as shell does:
|
||||||
|
|
||||||
@ -36,12 +36,12 @@ libalpm and AUR related configuration.
|
|||||||
Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` library to be installed.
|
Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` library to be installed.
|
||||||
|
|
||||||
* ``target`` - specifies authorization provider, string, optional, default ``disabled``. Allowed values are ``disabled``, ``configuration``, ``oauth``.
|
* ``target`` - specifies authorization provider, string, optional, default ``disabled``. Allowed values are ``disabled``, ``configuration``, ``oauth``.
|
||||||
|
* ``allow_read_only`` - allow requesting status APIs without authorization, boolean, required.
|
||||||
* ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used.
|
* ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used.
|
||||||
* ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used.
|
* ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used.
|
||||||
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
|
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
|
||||||
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
|
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
|
||||||
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
|
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
|
||||||
* ``safe_build_status`` - allow requesting status page without authorization, boolean, required.
|
|
||||||
* ``salt`` - password hash salt, string, required in case if authorization enabled (automatically generated by ``create-user`` subcommand).
|
* ``salt`` - password hash salt, string, required in case if authorization enabled (automatically generated by ``create-user`` subcommand).
|
||||||
|
|
||||||
Authorized users are stored inside internal database, if any of external provides are used the password field for non-service users must be empty.
|
Authorized users are stored inside internal database, if any of external provides are used the password field for non-service users must be empty.
|
||||||
@ -56,7 +56,7 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
|
|||||||
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
|
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
|
||||||
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
|
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
|
||||||
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
|
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
|
||||||
* ``triggers`` - list of trigger classes (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of mention.
|
* ``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.
|
||||||
|
|
||||||
``repository`` group
|
``repository`` group
|
||||||
--------------------
|
--------------------
|
||||||
@ -80,7 +80,7 @@ Settings for signing packages or repository. Group name can refer to architectur
|
|||||||
|
|
||||||
Report generation settings.
|
Report generation settings.
|
||||||
|
|
||||||
* ``target`` - list of reports to be generated, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. ``somerandomname`` must point to existing section, ``email`` must point to one of ``email`` of ``email:x86_64`` (the one with architecture has higher priority).
|
* ``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 ways:
|
||||||
|
|
||||||
@ -93,6 +93,7 @@ Type will be read from several ways:
|
|||||||
|
|
||||||
Section name must be either ``console`` (plus optional architecture name, e.g. ``console:x86_64``) or random name with ``type`` set.
|
Section name must be either ``console`` (plus optional architecture name, e.g. ``console:x86_64``) or random name with ``type`` set.
|
||||||
|
|
||||||
|
* ``type`` - type of the report, string, optional, must be set to ``console`` if exists.
|
||||||
* ``use_utf`` - use utf8 symbols in output if set and ascii otherwise, boolean, optional, default ``yes``.
|
* ``use_utf`` - use utf8 symbols in output if set and ascii otherwise, boolean, optional, default ``yes``.
|
||||||
|
|
||||||
``email`` type
|
``email`` type
|
||||||
@ -120,9 +121,9 @@ Section name must be either ``email`` (plus optional architecture name, e.g. ``e
|
|||||||
Section name must be either ``html`` (plus optional architecture name, e.g. ``html:x86_64``) or random name with ``type`` set.
|
Section name must be either ``html`` (plus optional architecture name, e.g. ``html:x86_64``) or random name with ``type`` set.
|
||||||
|
|
||||||
* ``type`` - type of the report, string, optional, must be set to ``html`` if exists.
|
* ``type`` - type of the report, string, optional, must be set to ``html`` if exists.
|
||||||
* ``path`` - path to html report file, string, required.
|
|
||||||
* ``homepage`` - link to homepage, string, optional.
|
* ``homepage`` - link to homepage, string, optional.
|
||||||
* ``link_path`` - prefix for HTML links, string, required.
|
* ``link_path`` - prefix for HTML links, string, required.
|
||||||
|
* ``path`` - path to html report file, string, required.
|
||||||
* ``template_path`` - path to Jinja2 template, string, required.
|
* ``template_path`` - path to Jinja2 template, string, required.
|
||||||
|
|
||||||
``telegram`` type
|
``telegram`` type
|
||||||
@ -137,6 +138,7 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
|
|||||||
* ``link_path`` - prefix for HTML links, string, required.
|
* ``link_path`` - prefix for HTML links, string, required.
|
||||||
* ``template_path`` - path to Jinja2 template, string, required.
|
* ``template_path`` - path to Jinja2 template, string, required.
|
||||||
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
||||||
|
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
||||||
|
|
||||||
``upload`` group
|
``upload`` group
|
||||||
----------------
|
----------------
|
||||||
@ -166,6 +168,7 @@ This feature requires Github key creation (see below). Section name must be eith
|
|||||||
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
|
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
|
||||||
|
|
||||||
* ``repository`` - Github repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
|
* ``repository`` - Github repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
|
||||||
|
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
||||||
* ``username`` - Github authorization user, string, required. Basically the same as ``owner``.
|
* ``username`` - Github authorization user, string, required. Basically the same as ``owner``.
|
||||||
|
|
||||||
``rsync`` type
|
``rsync`` type
|
||||||
|
@ -130,7 +130,9 @@ So it is the same as adding any other package, but due to restrictions you must
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
sudo -u ahriman ahriman package-add pacmann -s repository
|
sudo -u ahriman ahriman package-add pacman -s repository
|
||||||
|
|
||||||
|
This feature is heavily depends on local pacman cache. In order to use this feature it is recommended to either run ``pacman -Sy`` before the interaction or configure timer for this.
|
||||||
|
|
||||||
Package build fails because it cannot validate PGP signature of source files
|
Package build fails because it cannot validate PGP signature of source files
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -238,7 +240,7 @@ The default action (in case if no arguments provided) is ``repo-update``. Basica
|
|||||||
|
|
||||||
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 -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
|
||||||
|
|
||||||
By default, it runs ``repo-update``, but it can be overwritten to any other command you would like to, e.g.:
|
The action can be specified during run, e.g.:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
@ -690,7 +692,7 @@ You can also edit configuration and forward logs to ``stderr``, just change ``ha
|
|||||||
|
|
||||||
sed -i 's/handlers = syslog_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini
|
sed -i 's/handlers = syslog_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini
|
||||||
|
|
||||||
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_.
|
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_. The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger).
|
||||||
|
|
||||||
Html customization
|
Html customization
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -6,13 +6,14 @@ Wrapper for managing custom repository inspired by `repo-scripts <https://github
|
|||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* Install-configure-forget manager for own repository.
|
* Install-configure-forget manager for the very own repository.
|
||||||
* Multi-architecture support.
|
* Multi-architecture support.
|
||||||
* VCS packages support.
|
|
||||||
* Sign support with gpg (repository, package, per package settings).
|
|
||||||
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram) and even ability to write own extensions.
|
|
||||||
* Dependency manager.
|
* Dependency manager.
|
||||||
|
* VCS packages support.
|
||||||
|
* Official repository support.
|
||||||
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
||||||
|
* Sign support with gpg (repository, package, per package settings).
|
||||||
|
* 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.
|
* Repository status interface with optional authorization and control options.
|
||||||
|
|
||||||
Contents
|
Contents
|
||||||
@ -29,10 +30,3 @@ Contents
|
|||||||
advanced-usage
|
advanced-usage
|
||||||
triggers
|
triggers
|
||||||
modules
|
modules
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
------------------
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
@ -6,7 +6,7 @@ Initial setup
|
|||||||
#.
|
#.
|
||||||
Change settings if required, see :doc:`configuration reference <configuration>` for more details.
|
Change settings if required, see :doc:`configuration reference <configuration>` for more details.
|
||||||
#.
|
#.
|
||||||
TL;DR
|
TL;DR:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
@ -25,21 +25,24 @@ Initial setup
|
|||||||
Configure build tools (it is required for correct dependency management system):
|
Configure build tools (it is required for correct dependency management system):
|
||||||
|
|
||||||
#.
|
#.
|
||||||
Create build command, e.g. ``ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build`` (you can choose any name for command, basically it should be ``{name}-{arch}-build``).
|
Create build command (you can choose any name for command, basically it should be ``{name}-{arch}-build``):
|
||||||
#.
|
|
||||||
Create configuration file, e.g. ``cp /usr/share/devtools/pacman-{extra,ahriman}.conf`` (same as previous ``pacman-{name}.conf``).
|
|
||||||
#.
|
|
||||||
Change configuration file, add your own repository, add multilib repository etc;
|
|
||||||
#.
|
|
||||||
Set ``build_command`` option to point to your command.
|
|
||||||
#.
|
|
||||||
Configure ``/etc/sudoers.d/ahriman`` to allow running command without a password.
|
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build
|
ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build
|
||||||
|
|
||||||
|
#.
|
||||||
|
Create configuration file (same as previous ``pacman-{name}.conf``):
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
cp /usr/share/devtools/pacman-{extra,ahriman}.conf
|
cp /usr/share/devtools/pacman-{extra,ahriman}.conf
|
||||||
|
|
||||||
|
#.
|
||||||
|
Change configuration file, add your own repository, add multilib repository etc:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
echo '[multilib]' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
echo '[multilib]' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
||||||
echo 'Include = /etc/pacman.d/mirrorlist' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
echo 'Include = /etc/pacman.d/mirrorlist' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
||||||
|
|
||||||
@ -47,9 +50,19 @@ Initial setup
|
|||||||
echo 'SigLevel = Optional TrustAll' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
echo 'SigLevel = Optional TrustAll' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
||||||
echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/share/devtools/pacman-ahriman.conf
|
||||||
|
|
||||||
|
#.
|
||||||
|
Set ``build_command`` option to point to your command:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
echo '[build]' | tee -a /etc/ahriman.ini.d/build.ini
|
echo '[build]' | tee -a /etc/ahriman.ini.d/build.ini
|
||||||
echo 'build_command = ahriman-x86_64-build' | tee -a /etc/ahriman.ini.d/build.ini
|
echo 'build_command = ahriman-x86_64-build' | tee -a /etc/ahriman.ini.d/build.ini
|
||||||
|
|
||||||
|
#.
|
||||||
|
Configure ``/etc/sudoers.d/ahriman`` to allow running command without a password:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
echo 'Cmnd_Alias CARCHBUILD_CMD = /usr/local/bin/ahriman-x86_64-build *' | tee -a /etc/sudoers.d/ahriman
|
echo 'Cmnd_Alias CARCHBUILD_CMD = /usr/local/bin/ahriman-x86_64-build *' | tee -a /etc/sudoers.d/ahriman
|
||||||
echo 'ahriman ALL=(ALL) NOPASSWD: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
|
echo 'ahriman ALL=(ALL) NOPASSWD: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
|
||||||
chmod 400 /etc/sudoers.d/ahriman
|
chmod 400 /etc/sudoers.d/ahriman
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
Triggers
|
Triggers
|
||||||
========
|
========
|
||||||
|
|
||||||
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides two types of extensions - reporting and files uploading. Each extension must derive from the ``Trigger`` class and implement ``run`` method
|
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides two types of extensions - reporting and files uploading. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and implement ``run`` method
|
||||||
|
|
||||||
Trigger example
|
Trigger example
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Lets consider example of reporting trigger (e.g. `slack <https://slack.com/>`_, which provides easy HTTP API for triggers for integrations).
|
Lets consider example of reporting trigger (e.g. `slack <https://slack.com/>`_, which provides easy HTTP API for integration triggers).
|
||||||
|
|
||||||
In order to post message to slack we will need a specific trigger url (something like ``https://hooks.slack.com/services/company_id/trigger_id``), channel (e.g. ``#archrepo``) and username (``repo-bot``). It can be retrieved from the same application instance.
|
In order to post message to slack we will need a specific trigger url (something like ``https://hooks.slack.com/services/company_id/trigger_id``), channel (e.g. ``#archrepo``) and username (``repo-bot``).
|
||||||
|
|
||||||
As it has been mentioned, our trigger must derive from specific class
|
As it has been mentioned, our trigger must derive from specific class:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@ -24,9 +24,7 @@ As it has been mentioned, our trigger must derive from specific class
|
|||||||
self.channel = configuration.get("slack", "channel")
|
self.channel = configuration.get("slack", "channel")
|
||||||
self.username = configuration.get("slack", "username")
|
self.username = configuration.get("slack", "username")
|
||||||
|
|
||||||
By now we have class with all required variables.
|
By now we have class with all required variables. Lets implement run method. Slack API requires positing data with specific payload by HTTP, thus:
|
||||||
|
|
||||||
Lets implement run method. Slack API requires positing data with specific payload by HTTP, thus
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@ -39,7 +37,7 @@ Lets implement run method. Slack API requires positing data with specific payloa
|
|||||||
response = requests.post(slack_url, data={"payload": json.dumps(payload)})
|
response = requests.post(slack_url, data={"payload": json.dumps(payload)})
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
Obviously you can implement the specified method in class, but for guide purpose it has been done as separated method. Now we can merge this method into the class
|
Obviously you can implement the specified method in class, but for guide purpose it has been done as separated method. Now we can merge this method into the class:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@ -52,7 +50,7 @@ Obviously you can implement the specified method in class, but for guide purpose
|
|||||||
self.username = configuration.get("slack", "username")
|
self.username = configuration.get("slack", "username")
|
||||||
|
|
||||||
def run(self, result, packages):
|
def run(self, result, packages):
|
||||||
notify(result, self.slack_url, channel, username)
|
notify(result, self.slack_url, self.channel, self.username)
|
||||||
|
|
||||||
Setup the trigger
|
Setup the trigger
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# Maintainer: Evgeniy Alekseev
|
# Maintainer: Evgeniy Alekseev
|
||||||
|
|
||||||
pkgname='ahriman'
|
pkgname='ahriman'
|
||||||
pkgver=2.0.0rc10
|
pkgver=2.2.2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH Linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
url="https://github.com/arcan1s/ahriman"
|
url="https://github.com/arcan1s/ahriman"
|
||||||
license=('GPL3')
|
license=('GPL3')
|
||||||
depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo')
|
depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-requests' 'python-setuptools' 'python-srcinfo')
|
||||||
makedepends=('python-build' 'python-installer' 'python-wheel')
|
makedepends=('python-build' 'python-installer' 'python-wheel')
|
||||||
optdepends=('breezy: -bzr packages support'
|
optdepends=('breezy: -bzr packages support'
|
||||||
'darcs: -darcs packages support'
|
'darcs: -darcs packages support'
|
||||||
|
@ -1 +1 @@
|
|||||||
u ahriman 643 "ArcH Linux ReposItory MANager" /var/lib/ahriman
|
u ahriman 643 "ArcH linux ReposItory MANager" /var/lib/ahriman
|
@ -1,5 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=ArcH Linux ReposItory MANager web server (%I architecture)
|
Description=ArcH linux ReposItory MANager web server (%I architecture)
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=ArcH Linux ReposItory MANager (%I architecture)
|
Description=ArcH linux ReposItory MANager (%I architecture)
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/bin/ahriman --architecture %i update
|
ExecStart=/usr/bin/ahriman --architecture %i update
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=ArcH Linux ReposItory MANager timer (%I architecture)
|
Description=ArcH linux ReposItory MANager timer (%I architecture)
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=daily
|
OnCalendar=daily
|
||||||
|
@ -13,14 +13,14 @@ target = disabled
|
|||||||
max_age = 604800
|
max_age = 604800
|
||||||
oauth_provider = GoogleClient
|
oauth_provider = GoogleClient
|
||||||
oauth_scopes = https://www.googleapis.com/auth/userinfo.email
|
oauth_scopes = https://www.googleapis.com/auth/userinfo.email
|
||||||
safe_build_status = yes
|
allow_read_only = yes
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
archbuild_flags =
|
archbuild_flags =
|
||||||
build_command = extra-x86_64-build
|
build_command = extra-x86_64-build
|
||||||
ignore_packages =
|
ignore_packages =
|
||||||
makechrootpkg_flags =
|
makechrootpkg_flags =
|
||||||
makepkg_flags = --nocolor
|
makepkg_flags = --nocolor --ignorearch
|
||||||
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
|
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
|
||||||
|
|
||||||
[repository]
|
[repository]
|
||||||
@ -37,7 +37,6 @@ target = console
|
|||||||
use_utf = yes
|
use_utf = yes
|
||||||
|
|
||||||
[email]
|
[email]
|
||||||
full_template_path = /usr/share/ahriman/templates/repo-index.jinja2
|
|
||||||
no_empty_report = yes
|
no_empty_report = yes
|
||||||
template_path = /usr/share/ahriman/templates/email-index.jinja2
|
template_path = /usr/share/ahriman/templates/email-index.jinja2
|
||||||
ssl = disabled
|
ssl = disabled
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[loggers]
|
[loggers]
|
||||||
keys = root,build_details,database,http,stderr,boto3,botocore,nose,s3transfer
|
keys = root,http,stderr,boto3,botocore,nose,s3transfer
|
||||||
|
|
||||||
[handlers]
|
[handlers]
|
||||||
keys = console_handler,syslog_handler
|
keys = console_handler,syslog_handler
|
||||||
@ -20,11 +20,11 @@ formatter = syslog_format
|
|||||||
args = ("/dev/log",)
|
args = ("/dev/log",)
|
||||||
|
|
||||||
[formatter_generic_format]
|
[formatter_generic_format]
|
||||||
format = [%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s
|
format = [%(levelname)s %(asctime)s] [%(threadName)s] [%(name)s]: %(message)s
|
||||||
datefmt =
|
datefmt =
|
||||||
|
|
||||||
[formatter_syslog_format]
|
[formatter_syslog_format]
|
||||||
format = [%(levelname)s] [%(name)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s
|
format = [%(levelname)s] [%(threadName)s] [%(name)s]: %(message)s
|
||||||
datefmt =
|
datefmt =
|
||||||
|
|
||||||
[logger_root]
|
[logger_root]
|
||||||
@ -32,18 +32,6 @@ level = DEBUG
|
|||||||
handlers = syslog_handler
|
handlers = syslog_handler
|
||||||
qualname = root
|
qualname = root
|
||||||
|
|
||||||
[logger_build_details]
|
|
||||||
level = DEBUG
|
|
||||||
handlers = syslog_handler
|
|
||||||
qualname = build_details
|
|
||||||
propagate = 0
|
|
||||||
|
|
||||||
[logger_database]
|
|
||||||
level = DEBUG
|
|
||||||
handlers = syslog_handler
|
|
||||||
qualname = database
|
|
||||||
propagate = 0
|
|
||||||
|
|
||||||
[logger_http]
|
[logger_http]
|
||||||
level = DEBUG
|
level = DEBUG
|
||||||
handlers = syslog_handler
|
handlers = syslog_handler
|
||||||
|
@ -14,28 +14,29 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>ahriman
|
<h1>ahriman
|
||||||
{% if auth.authenticated %}
|
<img id="badge-version" src="https://img.shields.io/badge/version-unknown-informational" alt="unknown">
|
||||||
<img src="https://img.shields.io/badge/version-{{ version }}-informational" alt="{{ version }}">
|
<img id="badge-repository" src="https://img.shields.io/badge/repository-unknown-informational" alt="unknown">
|
||||||
<img src="https://img.shields.io/badge/repository-{{ repository | replace("-", "--") }}-informational" alt="{{ repository }}">
|
<img id="badge-architecture" src="https://img.shields.io/badge/architecture-unknown-informational" alt="unknown">
|
||||||
<img src="https://img.shields.io/badge/architecture-{{ architecture }}-informational" alt="{{ architecture }}">
|
<img id="badge-status" src="https://img.shields.io/badge/service%20status-unknown-inactive" alt="unknown">
|
||||||
<img src="https://img.shields.io/badge/service%20status-{{ service.status }}-{{ service.status_color }}" alt="{{ service.status }}" title="{{ service.timestamp }}">
|
|
||||||
{% endif %}
|
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
{% if not auth.enabled or auth.username is not none %}
|
{% if not auth.enabled or auth.username is not none %}
|
||||||
<button id="add" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addForm">
|
<button id="add-btn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-form" hidden>
|
||||||
<i class="fa fa-plus"></i> add
|
<i class="bi bi-plus"></i> add
|
||||||
</button>
|
</button>
|
||||||
<button id="update" class="btn btn-secondary" onclick="updatePackages()" disabled>
|
<button id="update-btn" class="btn btn-secondary" onclick="updatePackages()" disabled hidden>
|
||||||
<i class="fa fa-play"></i> update
|
<i class="bi bi-play"></i> update
|
||||||
</button>
|
</button>
|
||||||
<button id="remove" class="btn btn-danger" onclick="removePackages()" disabled>
|
<button id="remove-btn" class="btn btn-danger" onclick="removePackages()" disabled hidden>
|
||||||
<i class="fa fa-trash"></i> remove
|
<i class="bi bi-trash"></i> remove
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<button class="btn btn-secondary" onclick="reload()">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i> reload
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table id="packages" class="table table-striped table-hover"
|
<table id="packages" class="table table-striped table-hover"
|
||||||
@ -53,42 +54,22 @@
|
|||||||
data-show-fullscreen="true"
|
data-show-fullscreen="true"
|
||||||
data-show-search-clear-button="true"
|
data-show-search-clear-button="true"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
data-sort-reset="true"
|
data-sort-name="base"
|
||||||
|
data-sort-order="asc"
|
||||||
data-toggle="table"
|
data-toggle="table"
|
||||||
data-toolbar="#toolbar">
|
data-toolbar="#toolbar">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
<th data-checkbox="true"></th>
|
<th data-checkbox="true"></th>
|
||||||
<th data-sortable="true" data-switchable="false">package base</th>
|
<th data-sortable="true" data-switchable="false" data-field="base">package base</th>
|
||||||
<th data-sortable="true">version</th>
|
<th data-sortable="true" data-field="version">version</th>
|
||||||
<th data-sortable="true">packages</th>
|
<th data-sortable="true" data-field="packages">packages</th>
|
||||||
<th data-sortable="true" data-visible="false">groups</th>
|
<th data-sortable="true" data-visible="false" data-field="groups">groups</th>
|
||||||
<th data-sortable="true" data-visible="false">licenses</th>
|
<th data-sortable="true" data-visible="false" data-field="licenses">licenses</th>
|
||||||
<th data-sortable="true">last update</th>
|
<th data-sortable="true" data-field="timestamp">last update</th>
|
||||||
<th data-sortable="true">status</th>
|
<th data-sortable="true" data-cell-style="statusFormat" data-field="status">status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{% if auth.authenticated %}
|
|
||||||
{% for package in packages %}
|
|
||||||
<tr data-package-base="{{ package.base }}">
|
|
||||||
<td data-checkbox="true"></td>
|
|
||||||
<td>{% if package.web_url is not none %}<a href="{{ package.web_url }}" title="{{ package.base }}">{{ package.base }}</a>{% else %}{{ package.base }}{% endif %}</td>
|
|
||||||
<td>{{ package.version }}</td>
|
|
||||||
<td>{{ package.packages|join("<br>"|safe) }}</td>
|
|
||||||
<td>{{ package.groups|join("<br>"|safe) }}</td>
|
|
||||||
<td>{{ package.licenses|join("<br>"|safe) }}</td>
|
|
||||||
<td>{{ package.timestamp }}</td>
|
|
||||||
<td class="table-{{ package.status_color }}">{{ package.status }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="100%">In order to see statuses you must login first.</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -110,7 +91,7 @@
|
|||||||
{% if auth.username is none %}
|
{% if auth.username is none %}
|
||||||
{{ auth.control|safe }}
|
{{ auth.control|safe }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<form action="/user-api/v1/logout" method="post">
|
<form action="/api/v1/logout" method="post">
|
||||||
<button class="btn btn-link" style="text-decoration: none">logout ({{ auth.username }})</button>
|
<button class="btn btn-link" style="text-decoration: none">logout ({{ auth.username }})</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -122,11 +103,14 @@
|
|||||||
{% include "build-status/login-modal.jinja2" %}
|
{% include "build-status/login-modal.jinja2" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% include "build-status/package-actions-modals.jinja2" %}
|
|
||||||
|
|
||||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||||
|
|
||||||
{% include "build-status/package-actions-script.jinja2" %}
|
{% include "build-status/failed-modal.jinja2" %}
|
||||||
|
{% include "build-status/success-modal.jinja2" %}
|
||||||
|
|
||||||
|
{% include "build-status/package-add-modal.jinja2" %}
|
||||||
|
|
||||||
|
{% include "build-status/table.jinja2" %}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<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>
|
@ -1,7 +1,7 @@
|
|||||||
<div id="loginForm" tabindex="-1" role="dialog" class="modal fade">
|
<div id="loginForm" tabindex="-1" role="dialog" class="modal fade">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<form action="/user-api/v1/login" method="post">
|
<form action="/api/v1/login" method="post">
|
||||||
<div class="modal-header">
|
<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>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
<div id="addForm" 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" type="text" list="knownPackages" class="form-control" placeholder="AUR package" name="package" required>
|
|
||||||
<datalist id="knownPackages"></datalist>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="failedForm" 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="errorDetails"></p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="successForm" 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="successDetails"></ul>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,95 +0,0 @@
|
|||||||
<script>
|
|
||||||
const $remove = $("#remove");
|
|
||||||
const $update = $("#update");
|
|
||||||
|
|
||||||
const $table = $("#packages");
|
|
||||||
$table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table",
|
|
||||||
function () {
|
|
||||||
$remove.prop("disabled", !$table.bootstrapTable("getSelections").length);
|
|
||||||
$update.prop("disabled", !$table.bootstrapTable("getSelections").length);
|
|
||||||
})
|
|
||||||
|
|
||||||
const $successForm = $("#successForm");
|
|
||||||
const $successDetails = $("#successDetails");
|
|
||||||
$successForm.on("hidden.bs.modal", function() { window.location.reload(); });
|
|
||||||
|
|
||||||
const $failedForm = $("#failedForm");
|
|
||||||
const $errorDetails = $("#errorDetails");
|
|
||||||
$failedForm.on("hidden.bs.modal", function() { window.location.reload(); });
|
|
||||||
|
|
||||||
const $package = $("#package");
|
|
||||||
const $knownPackages = $("#knownPackages");
|
|
||||||
$package.keyup(function () {
|
|
||||||
const $this = $(this);
|
|
||||||
clearTimeout($this.data("timeout"));
|
|
||||||
|
|
||||||
$this.data("timeout", setTimeout($.proxy(function () {
|
|
||||||
const $value = $package.val();
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: "/service-api/v1/search",
|
|
||||||
data: {"for": $value},
|
|
||||||
type: "GET",
|
|
||||||
dataType: "json",
|
|
||||||
success: function (resp) {
|
|
||||||
const $options = resp.map(function (pkg) {
|
|
||||||
const $option = document.createElement("option");
|
|
||||||
$option.value = pkg.package;
|
|
||||||
$option.innerText = `${pkg.package} (${pkg.description})`;
|
|
||||||
return $option;
|
|
||||||
});
|
|
||||||
$knownPackages.empty().append($options);
|
|
||||||
$this.focus();
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}, this), 500));
|
|
||||||
})
|
|
||||||
|
|
||||||
function doPackageAction($uri, $packages) {
|
|
||||||
if ($packages.length === 0)
|
|
||||||
return;
|
|
||||||
$.ajax({
|
|
||||||
url: $uri,
|
|
||||||
data: JSON.stringify({packages: $packages}),
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json",
|
|
||||||
success: function (_) {
|
|
||||||
const $details = $packages.map(function (pkg) {
|
|
||||||
const $li = document.createElement("li");
|
|
||||||
$li.innerText = pkg;
|
|
||||||
return $li;
|
|
||||||
});
|
|
||||||
$successDetails.empty().append($details);
|
|
||||||
$successForm.modal("show");
|
|
||||||
},
|
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
|
||||||
$errorDetails.text(errorThrown);
|
|
||||||
$failedForm.modal("show");
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSelection() {
|
|
||||||
return $.map($table.bootstrapTable("getSelections"), function(row) {
|
|
||||||
return row._data["package-base"];
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPackages() {
|
|
||||||
const $packages = [$package.val()]
|
|
||||||
doPackageAction("/service-api/v1/add", $packages);
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestPackages() {
|
|
||||||
const $packages = [$package.val()]
|
|
||||||
doPackageAction("/service-api/v1/request", $packages);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePackages() { doPackageAction("/service-api/v1/remove", getSelection()); }
|
|
||||||
|
|
||||||
function updatePackages() { doPackageAction("/service-api/v1/add", getSelection()); }
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$table.bootstrapTable("uncheckAll");
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -0,0 +1,61 @@
|
|||||||
|
<div id="add-form" 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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const packageInput = $("#package-form");
|
||||||
|
const knownPackages = $("#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);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, this), 500));
|
||||||
|
});
|
||||||
|
|
||||||
|
function addPackages() {
|
||||||
|
const packages = [packageInput.val()];
|
||||||
|
doPackageAction("/api/v1/service/add", packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestPackages() {
|
||||||
|
const packages = [packageInput.val()];
|
||||||
|
doPackageAction("/api/v1/service/request", packages);
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,28 @@
|
|||||||
|
<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>
|
160
package/share/ahriman/templates/build-status/table.jinja2
Normal file
160
package/share/ahriman/templates/build-status/table.jinja2
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<script>
|
||||||
|
const addButton = $("#add-btn");
|
||||||
|
const removeButton = $("#remove-btn");
|
||||||
|
const updateButton = $("#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);
|
||||||
|
updateButton.prop("disabled", !table.bootstrapTable("getSelections").length);
|
||||||
|
});
|
||||||
|
|
||||||
|
const architectureBadge = $("#badge-architecture");
|
||||||
|
const repositoryBadge = $("#badge-repository");
|
||||||
|
const statusBadge = $("#badge-status");
|
||||||
|
const versionBadge = $("#badge-version");
|
||||||
|
|
||||||
|
function doPackageAction(uri, packages) {
|
||||||
|
if (packages.length === 0)
|
||||||
|
return;
|
||||||
|
$.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);
|
||||||
|
},
|
||||||
|
error: (jqXHR, _, errorThrown) => { showFailure(errorThrown); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelection() {
|
||||||
|
return table.bootstrapTable("getSelections").map(row => { return row.id; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePackages() { doPackageAction("/api/v1/service/remove", getSelection()); }
|
||||||
|
|
||||||
|
function updatePackages() { doPackageAction("/api/v1/service/add", getSelection()); }
|
||||||
|
|
||||||
|
function hideControls(hidden) {
|
||||||
|
addButton.attr("hidden", hidden);
|
||||||
|
removeButton.attr("hidden", hidden);
|
||||||
|
updateButton.attr("hidden", hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
table.bootstrapTable("showLoading");
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/v1/packages",
|
||||||
|
type: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
success: response => {
|
||||||
|
const extractListProperties = (description, property) => {
|
||||||
|
return Object.values(description.packages)
|
||||||
|
.map(pkg => { return pkg[property]; })
|
||||||
|
.reduce((left, right) => { return left.concat(right); }, []);
|
||||||
|
};
|
||||||
|
const listToTable = data => {
|
||||||
|
return Array.from(new Set(data))
|
||||||
|
.sort()
|
||||||
|
.map(entry => { return safe(entry); })
|
||||||
|
.join("<br>");
|
||||||
|
};
|
||||||
|
|
||||||
|
const payload = response.map(description => {
|
||||||
|
const package_base = description.package.base;
|
||||||
|
const web_url = description.package.remote?.web_url;
|
||||||
|
return {
|
||||||
|
id: package_base,
|
||||||
|
base: web_url ? `<a href="${safe(web_url)}" title="${safe(package_base)}">${safe(package_base)}</a>` : safe(package_base),
|
||||||
|
version: safe(description.package.version),
|
||||||
|
packages: listToTable(Object.keys(description.package.packages)),
|
||||||
|
groups: listToTable(extractListProperties(description.package, "groups")),
|
||||||
|
licenses: listToTable(extractListProperties(description.package, "licenses")),
|
||||||
|
timestamp: new Date(1000 * description.status.timestamp).toISOString(),
|
||||||
|
status: description.status.status,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
table.bootstrapTable("load", payload);
|
||||||
|
table.bootstrapTable("uncheckAll");
|
||||||
|
table.bootstrapTable("hideLoading");
|
||||||
|
hideControls(false);
|
||||||
|
},
|
||||||
|
error: (jqXHR, _, errorThrown) => {
|
||||||
|
if ((jqXHR.status === 401) || (jqXHR.status === 403)) {
|
||||||
|
// authorization error
|
||||||
|
const text = "In order to see statuses you must login first.";
|
||||||
|
table.find("tr.unauthorized").remove();
|
||||||
|
table.find("tbody").append(`<tr class="unauthorized"><td colspan="100%">${safe(text)}</td></tr>`);
|
||||||
|
table.bootstrapTable("hideLoading");
|
||||||
|
} else {
|
||||||
|
// other errors
|
||||||
|
showFailure(errorThrown);
|
||||||
|
}
|
||||||
|
hideControls(true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/v1/status",
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
if (status === "building") return "table-warning";
|
||||||
|
if (status === "failed") return "table-danger";
|
||||||
|
if (status === "success") return "table-success";
|
||||||
|
return "table-secondary";
|
||||||
|
};
|
||||||
|
return {classes: cellClass(value)};
|
||||||
|
}
|
||||||
|
|
||||||
|
$(() => {
|
||||||
|
table.bootstrapTable({});
|
||||||
|
reload();
|
||||||
|
});
|
||||||
|
</script>
|
@ -11,7 +11,7 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Arch Linux user repository</h1>
|
<h1>Arch linux user repository</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -40,7 +40,8 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
data-show-fullscreen="true"
|
data-show-fullscreen="true"
|
||||||
data-show-search-clear-button="true"
|
data-show-search-clear-button="true"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
data-sort-reset="true"
|
data-sort-name="base"
|
||||||
|
data-sort-order="asc"
|
||||||
data-toggle="table">
|
data-toggle="table">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
|
18
package/share/ahriman/templates/shell
Normal file
18
package/share/ahriman/templates/shell
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[00m
|
||||||
|
[38;5;60m▄[48;5;60;38;5;67m▄[49;38;5;60m▄[39m [38;5;60m▄▄▄▄▄▄[48;5;60m█[49m▀[39m [00m
|
||||||
|
[38;5;60m▄[48;5;60;38;5;67m▄[48;5;67;38;5;60m▄[48;5;60;38;5;103m▄[48;5;103;38;5;110m▄[48;5;60;38;5;103m▄[38;5;67m▄▄▄[48;5;67m██[38;5;60m▄▄▄[38;5;67m█[48;5;60m▄[49;38;5;60m▄[39m [00m
|
||||||
|
[48;5;60;38;5;60m█[48;5;67;38;5;67m█[48;5;60;38;5;103m▄[48;5;103;38;5;110m▄[48;5;110m██[48;5;103;38;5;103m█[48;5;60;38;5;67m▄▄▄▄[48;5;67;38;5;60m▄[38;5;67m██[48;5;60m▄[48;5;67;38;5;60m▄[38;5;67m█[48;5;60m▄[49;38;5;60m▄[39m [00m
|
||||||
|
[48;5;60;38;5;60m█[48;5;67m▄[48;5;103;38;5;103m█[48;5;110;38;5;110m█[48;5;103;38;5;103m█[48;5;110;38;5;110m██[48;5;103;38;5;103m█[48;5;60;38;5;110m▄▄[38;5;103m▄[48;5;110;38;5;110m█[48;5;60;38;5;60m█[48;5;67;38;5;67m██[48;5;60m▄[48;5;67;38;5;60m▄[38;5;67m█[48;5;60m▄[49;38;5;60m▄[39m [38;5;60m▄[39m[00m
|
||||||
|
[48;5;60;38;5;60m█[38;5;67m▄[48;5;103;38;5;60m▄[48;5;110;38;5;103m▄[38;5;110m█████████[48;5;60m▄▄[48;5;67;38;5;60m▄▄[49m▀[48;5;67m▄[38;5;67m█[48;5;60m▄[38;5;60m█[49m▀[39m[00m
|
||||||
|
[48;5;60;38;5;60m█[49m▄▀[48;5;67m▄[48;5;103;38;5;103m█[48;5;110;38;5;110m███[38;5;232m▄[38;5;110m█[38;5;232m▄▄▄▄[38;5;110m███[48;5;103m▄[49;38;5;103m▄[39m [38;5;60m▀▀[39m [00m
|
||||||
|
[48;5;60;38;5;60m█[48;5;67m▄[48;5;60;38;5;67m▄▄[48;5;103;38;5;103m█[48;5;110;38;5;110m███[38;5;232m▄[48;5;232m█[48;5;188;38;5;188m█[48;5;231;38;5;231m██[48;5;232;38;5;232m██[48;5;110;38;5;110m███[48;5;103;38;5;103m█[49;39m [00m
|
||||||
|
[38;5;60m▄▄[48;5;60;38;5;67m▄▄▄▄[49;38;5;60m▄▄▄[39m [38;5;60m▀[48;5;60m█[38;5;67m▄[48;5;103;38;5;103m█[48;5;110;38;5;110m████[48;5;232m▄[48;5;145;38;5;231m▄[48;5;232;38;5;145m▄[38;5;231m▄[38;5;232m█[38;5;231m▄[48;5;110;38;5;110m██[48;5;103;38;5;103m█[49m▄[39m [00m
|
||||||
|
[38;5;60m▄[48;5;60;38;5;67m▄[48;5;67m███[38;5;60m▄▄▄▄▄▄[48;5;60;38;5;67m▄▄[49;38;5;60m▄[39m [38;5;60m▀[39m [38;5;103m▀[48;5;110m▄[38;5;110m█████[48;5;188m▄▄[48;5;110m█[38;5;103m▄[38;5;110m██[48;5;103;38;5;103m█[49;39m [00m
|
||||||
|
[48;5;60;38;5;60m█[48;5;67;38;5;67m██[38;5;60m▄[48;5;60;38;5;67m▄[48;5;67m████[38;5;60m▄▄[48;5;60m█[48;5;67m▄[38;5;67m█[48;5;60;38;5;60m█[49;38;5;103m▄▄▄[39m [48;5;103;38;5;103m█[48;5;110;38;5;110m██[48;5;103m▄[48;5;110;38;5;103m▄▄▄▄▄[48;5;103m█[49m▀▀[39m [00m
|
||||||
|
[48;5;60;38;5;60m█[48;5;67;38;5;67m█[48;5;60;38;5;60m█[48;5;67;38;5;67m████[38;5;60m▄[49m▀[39m [48;5;60;38;5;103m▄[48;5;103;38;5;110m▄[48;5;110;38;5;179m▄[38;5;110m██[48;5;103m▄▄▄[48;5;110m████[48;5;103;38;5;103m█[49;39m [00m
|
||||||
|
[48;5;60;38;5;60m█[48;5;67;38;5;67m█[48;5;60m▄[48;5;67;38;5;60m▄[38;5;67m███[48;5;60;38;5;60m█[49;39m [48;5;103;38;5;103m█[48;5;185;38;5;110m▄[48;5;110m█[38;5;179m▄[48;5;179;38;5;110m▄[48;5;110m████████[48;5;103;38;5;103m█[49;39m [00m
|
||||||
|
[38;5;60m▄[48;5;60;38;5;67m▄[48;5;67m█[38;5;60m▄[48;5;60;38;5;67m▄[48;5;67m██[38;5;60m▄[49m▀[39m [38;5;103m▀[48;5;110m▄[38;5;110m████[38;5;103m▄[38;5;110m██[38;5;103m▄[38;5;110m██[38;5;103m▄[48;5;103;38;5;67m▄[49;39m [00m
|
||||||
|
[48;5;60;38;5;60m█[48;5;67;38;5;67m██[48;5;60;38;5;60m█[48;5;67;38;5;67m███[48;5;60;38;5;60m█[49;39m [38;5;103m▄[48;5;103m█[48;5;110;38;5;110m██[38;5;103m▄[48;5;103;38;5;110m▄[38;5;67m▄[38;5;103m█[38;5;110m▄[48;5;110m█[48;5;103;38;5;103m█[48;5;110;38;5;110m█[48;5;67;38;5;67m█[49;39m [00m
|
||||||
|
[38;5;60m▄[48;5;60;38;5;67m▄[48;5;67;38;5;60m▄[48;5;60;38;5;67m▄[48;5;67m███[38;5;60m▄[49m▀[39m [38;5;103m▄[48;5;103;38;5;110m▄[48;5;110m██[48;5;103;38;5;103m█[48;5;110;38;5;110m█[48;5;67;38;5;67m█[49;39m [48;5;103;38;5;103m█[48;5;110;38;5;110m██[48;5;103m▄[48;5;110;38;5;103m▄[48;5;67;38;5;110m▄[49;38;5;67m▄[39m [00m
|
||||||
|
[38;5;60m▄▄▄[48;5;60m█[48;5;67;38;5;67m█[38;5;60m▄[48;5;60;38;5;67m▄[48;5;67m█[38;5;60m▄▄▄[49m▀[39m [48;5;103;38;5;103m█[48;5;110;38;5;110m███[48;5;103;38;5;103m█[48;5;110;38;5;67m▄[48;5;67m█[49;39m [48;5;103;38;5;103m█[48;5;110;38;5;110m███[48;5;103;38;5;103m█[48;5;110;38;5;67m▄[48;5;67m█[49;39m [00m
|
||||||
|
[38;5;60m▀▀▀▀▀▀▀[39m [38;5;103m▀▀▀▀[39m [38;5;103m▀▀▀▀[39m [00m
|
@ -4,20 +4,9 @@
|
|||||||
|
|
||||||
<script src="https://unpkg.com/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js"></script>
|
<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.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></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.18.3/dist/bootstrap-table.min.js"></script>
|
<script src="https://unpkg.com/bootstrap-table@1.20.2/dist/bootstrap-table.min.js"></script>
|
||||||
|
|
||||||
<script src="https://unpkg.com/bootstrap-table@1.18.3/dist/extensions/export/bootstrap-table-export.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.18.3/dist/extensions/resizable/bootstrap-table-resizable.js"></script>
|
<script src="https://unpkg.com/bootstrap-table@1.20.2/dist/extensions/resizable/bootstrap-table-resizable.js"></script>
|
||||||
|
|
||||||
<script>
|
|
||||||
$("#packages").bootstrapTable({
|
|
||||||
formatClearSearch: function () {
|
|
||||||
return "Clear search";
|
|
||||||
},
|
|
||||||
formatSearch: function () {
|
|
||||||
return "search";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script src="https://kit.fontawesome.com/0d6d6d5226.js" crossorigin="anonymous"></script>
|
<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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.3/font/bootstrap-icons.css" type="text/css">
|
||||||
|
|
||||||
<link href="https://unpkg.com/bootstrap-table@1.18.3/dist/bootstrap-table.min.css" rel="stylesheet">
|
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.20.2/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 href="https://unpkg.com/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" rel="stylesheet">
|
||||||
|
|
||||||
|
12
setup.py
12
setup.py
@ -1,5 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from setuptools import setup, find_packages
|
from setuptools import find_packages, setup
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ setup(
|
|||||||
version=metadata["__version__"],
|
version=metadata["__version__"],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|
||||||
description="ArcH Linux ReposItory MANager",
|
description="ArcH linux ReposItory MANager",
|
||||||
|
|
||||||
author="ahriman team",
|
author="ahriman team",
|
||||||
author_email="",
|
author_email="",
|
||||||
@ -32,6 +32,7 @@ setup(
|
|||||||
"inflection",
|
"inflection",
|
||||||
"passlib",
|
"passlib",
|
||||||
"requests",
|
"requests",
|
||||||
|
"setuptools",
|
||||||
"srcinfo",
|
"srcinfo",
|
||||||
],
|
],
|
||||||
setup_requires=[
|
setup_requires=[
|
||||||
@ -66,12 +67,15 @@ setup(
|
|||||||
"package/share/ahriman/templates/build-status.jinja2",
|
"package/share/ahriman/templates/build-status.jinja2",
|
||||||
"package/share/ahriman/templates/email-index.jinja2",
|
"package/share/ahriman/templates/email-index.jinja2",
|
||||||
"package/share/ahriman/templates/repo-index.jinja2",
|
"package/share/ahriman/templates/repo-index.jinja2",
|
||||||
|
"package/share/ahriman/templates/shell",
|
||||||
"package/share/ahriman/templates/telegram-index.jinja2",
|
"package/share/ahriman/templates/telegram-index.jinja2",
|
||||||
]),
|
]),
|
||||||
("share/ahriman/templates/build-status", [
|
("share/ahriman/templates/build-status", [
|
||||||
|
"package/share/ahriman/templates/build-status/failed-modal.jinja2",
|
||||||
"package/share/ahriman/templates/build-status/login-modal.jinja2",
|
"package/share/ahriman/templates/build-status/login-modal.jinja2",
|
||||||
"package/share/ahriman/templates/build-status/package-actions-modals.jinja2",
|
"package/share/ahriman/templates/build-status/package-add-modal.jinja2",
|
||||||
"package/share/ahriman/templates/build-status/package-actions-script.jinja2",
|
"package/share/ahriman/templates/build-status/success-modal.jinja2",
|
||||||
|
"package/share/ahriman/templates/build-status/table.jinja2",
|
||||||
]),
|
]),
|
||||||
("share/ahriman/templates/static", [
|
("share/ahriman/templates/static", [
|
||||||
"package/share/ahriman/templates/static/favicon.ico",
|
"package/share/ahriman/templates/static/favicon.ico",
|
||||||
|
@ -26,6 +26,7 @@ from typing import List, TypeVar
|
|||||||
|
|
||||||
from ahriman import version
|
from ahriman import version
|
||||||
from ahriman.application import handlers
|
from ahriman.application import handlers
|
||||||
|
from ahriman.core.util import enum_values
|
||||||
from ahriman.models.action import Action
|
from ahriman.models.action import Action
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
from ahriman.models.package_source import PackageSource
|
from ahriman.models.package_source import PackageSource
|
||||||
@ -63,7 +64,7 @@ def _parser() -> argparse.ArgumentParser:
|
|||||||
Returns:
|
Returns:
|
||||||
argparse.ArgumentParser: command line parser for the application
|
argparse.ArgumentParser: command line parser for the application
|
||||||
"""
|
"""
|
||||||
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH Linux ReposItory MANager",
|
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH linux ReposItory MANager",
|
||||||
epilog="Argument list can also be read from file by using @ prefix.",
|
epilog="Argument list can also be read from file by using @ prefix.",
|
||||||
fromfile_prefix_chars="@", formatter_class=_formatter)
|
fromfile_prefix_chars="@", formatter_class=_formatter)
|
||||||
parser.add_argument("-a", "--architecture", help="target architectures (can be used multiple times)",
|
parser.add_argument("-a", "--architecture", help="target architectures (can be used multiple times)",
|
||||||
@ -76,7 +77,7 @@ def _parser() -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
|
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
|
||||||
parser.add_argument("--unsafe", help="allow to run ahriman as non-ahriman user. Some actions might be unavailable",
|
parser.add_argument("--unsafe", help="allow to run ahriman as non-ahriman user. Some actions might be unavailable",
|
||||||
action="store_true")
|
action="store_true")
|
||||||
parser.add_argument("-v", "--version", action="version", version=version.__version__)
|
parser.add_argument("-V", "--version", action="version", version=version.__version__)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(title="command", help="command to run", dest="command", required=True)
|
subparsers = parser.add_subparsers(title="command", help="command to run", dest="command", required=True)
|
||||||
|
|
||||||
@ -98,15 +99,19 @@ def _parser() -> argparse.ArgumentParser:
|
|||||||
_set_repo_config_parser(subparsers)
|
_set_repo_config_parser(subparsers)
|
||||||
_set_repo_rebuild_parser(subparsers)
|
_set_repo_rebuild_parser(subparsers)
|
||||||
_set_repo_remove_unknown_parser(subparsers)
|
_set_repo_remove_unknown_parser(subparsers)
|
||||||
|
_set_repo_report_parser(subparsers)
|
||||||
_set_repo_restore_parser(subparsers)
|
_set_repo_restore_parser(subparsers)
|
||||||
_set_repo_setup_parser(subparsers)
|
_set_repo_setup_parser(subparsers)
|
||||||
_set_repo_sign_parser(subparsers)
|
_set_repo_sign_parser(subparsers)
|
||||||
_set_repo_status_update_parser(subparsers)
|
_set_repo_status_update_parser(subparsers)
|
||||||
|
_set_repo_sync_parser(subparsers)
|
||||||
_set_repo_triggers_parser(subparsers)
|
_set_repo_triggers_parser(subparsers)
|
||||||
_set_repo_update_parser(subparsers)
|
_set_repo_update_parser(subparsers)
|
||||||
|
_set_shell_parser(subparsers)
|
||||||
_set_user_add_parser(subparsers)
|
_set_user_add_parser(subparsers)
|
||||||
_set_user_list_parser(subparsers)
|
_set_user_list_parser(subparsers)
|
||||||
_set_user_remove_parser(subparsers)
|
_set_user_remove_parser(subparsers)
|
||||||
|
_set_version_parser(subparsers)
|
||||||
_set_web_parser(subparsers)
|
_set_web_parser(subparsers)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
@ -223,7 +228,7 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||||
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
|
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
|
||||||
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
||||||
type=PackageSource, choices=PackageSource, default=PackageSource.Auto)
|
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
|
||||||
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
|
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.Add)
|
parser.set_defaults(handler=handlers.Add)
|
||||||
return parser
|
return parser
|
||||||
@ -265,7 +270,7 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
|
|||||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
parser.add_argument("-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("-i", "--info", help="show additional package information", action="store_true")
|
||||||
parser.add_argument("-s", "--status", help="filter packages by status",
|
parser.add_argument("-s", "--status", help="filter packages by status",
|
||||||
type=BuildStatusEnum, choices=BuildStatusEnum)
|
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
|
||||||
parser.set_defaults(handler=handlers.Status, lock=None, no_report=True, quiet=True, unsafe=True)
|
parser.set_defaults(handler=handlers.Status, lock=None, no_report=True, quiet=True, unsafe=True)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -307,7 +312,7 @@ def _set_package_status_update_parser(root: SubParserAction) -> argparse.Argumen
|
|||||||
"If no packages supplied, service status will be updated",
|
"If no packages supplied, service status will be updated",
|
||||||
nargs="*")
|
nargs="*")
|
||||||
parser.add_argument("-s", "--status", help="new status",
|
parser.add_argument("-s", "--status", help="new status",
|
||||||
type=BuildStatusEnum, choices=BuildStatusEnum, default=BuildStatusEnum.Success)
|
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
|
||||||
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, no_report=True, quiet=True,
|
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, no_report=True, quiet=True,
|
||||||
unsafe=True)
|
unsafe=True)
|
||||||
return parser
|
return parser
|
||||||
@ -495,6 +500,24 @@ def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentP
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
add parser for report subcommand
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root(SubParserAction): subparsers for the commands
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
argparse.ArgumentParser: created argument parser
|
||||||
|
"""
|
||||||
|
parser = root.add_parser("repo-report", aliases=["report"], help="generate report",
|
||||||
|
description="generate repository report according to current settings",
|
||||||
|
epilog="Create and/or update repository report as configured.",
|
||||||
|
formatter_class=_formatter)
|
||||||
|
parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.report.ReportTrigger"])
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
"""
|
"""
|
||||||
add parser for repository restore subcommand
|
add parser for repository restore subcommand
|
||||||
@ -536,7 +559,7 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--repository", help="repository name", required=True)
|
parser.add_argument("--repository", help="repository name", required=True)
|
||||||
parser.add_argument("--sign-key", help="sign key id")
|
parser.add_argument("--sign-key", help="sign key id")
|
||||||
parser.add_argument("--sign-target", help="sign options", action="append",
|
parser.add_argument("--sign-target", help="sign options", action="append",
|
||||||
type=SignSettings.from_option, choices=SignSettings)
|
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-port", help="port of the web service", type=int)
|
||||||
parser.set_defaults(handler=handlers.Setup, lock=None, no_report=True, quiet=True, unsafe=True)
|
parser.set_defaults(handler=handlers.Setup, lock=None, no_report=True, quiet=True, unsafe=True)
|
||||||
return parser
|
return parser
|
||||||
@ -574,16 +597,34 @@ def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentPa
|
|||||||
parser = root.add_parser("repo-status-update", help="update repository status",
|
parser = root.add_parser("repo-status-update", help="update repository status",
|
||||||
description="update repository status on the status page", formatter_class=_formatter)
|
description="update repository status on the status page", formatter_class=_formatter)
|
||||||
parser.add_argument("-s", "--status", help="new status",
|
parser.add_argument("-s", "--status", help="new status",
|
||||||
type=BuildStatusEnum, choices=BuildStatusEnum, default=BuildStatusEnum.Success)
|
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
|
||||||
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, no_report=True, package=[],
|
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, no_report=True, package=[],
|
||||||
quiet=True, unsafe=True)
|
quiet=True, unsafe=True)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
"""
|
"""
|
||||||
add parser for repository sync subcommand
|
add parser for repository sync subcommand
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root(SubParserAction): subparsers for the commands
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
argparse.ArgumentParser: created argument parser
|
||||||
|
"""
|
||||||
|
parser = root.add_parser("repo-sync", aliases=["sync"], help="sync repository",
|
||||||
|
description="sync repository files to remote server according to current settings",
|
||||||
|
epilog="Synchronize the repository to remote services as configured.",
|
||||||
|
formatter_class=_formatter)
|
||||||
|
parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.upload.UploadTrigger"])
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
add parser for repository triggers subcommand
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
root(SubParserAction): subparsers for the commands
|
root(SubParserAction): subparsers for the commands
|
||||||
|
|
||||||
@ -593,6 +634,8 @@ def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser = root.add_parser("repo-triggers", help="run triggers",
|
parser = root.add_parser("repo-triggers", help="run triggers",
|
||||||
description="run triggers on empty build result as configured by settings",
|
description="run triggers on empty build result as configured by settings",
|
||||||
formatter_class=_formatter)
|
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="*")
|
||||||
parser.set_defaults(handler=handlers.Triggers)
|
parser.set_defaults(handler=handlers.Triggers)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -621,6 +664,24 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _set_shell_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
add parser for shell subcommand
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root(SubParserAction): subparsers for the commands
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
argparse.ArgumentParser: created argument parser
|
||||||
|
"""
|
||||||
|
parser = root.add_parser("shell", help="envoke python shell",
|
||||||
|
description="drop into python shell while having created application",
|
||||||
|
formatter_class=_formatter)
|
||||||
|
parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true")
|
||||||
|
parser.set_defaults(handler=handlers.Shell, lock=None, no_report=True)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
"""
|
"""
|
||||||
add parser for create user subcommand
|
add parser for create user subcommand
|
||||||
@ -640,7 +701,7 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("-p", "--password", help="user password. Blank password will be treated as empty password, "
|
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.")
|
"which is in particular must be used for OAuth2 authorization type.")
|
||||||
parser.add_argument("-r", "--role", help="user access level",
|
parser.add_argument("-r", "--role", help="user access level",
|
||||||
type=UserAccess, choices=UserAccess, default=UserAccess.Read)
|
type=UserAccess, choices=enum_values(UserAccess), default=UserAccess.Read)
|
||||||
parser.add_argument("-s", "--secure", help="set file permissions to user-only", action="store_true")
|
parser.add_argument("-s", "--secure", help="set file permissions to user-only", action="store_true")
|
||||||
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, no_report=True,
|
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, no_report=True,
|
||||||
quiet=True, unsafe=True)
|
quiet=True, unsafe=True)
|
||||||
@ -662,7 +723,7 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
formatter_class=_formatter)
|
formatter_class=_formatter)
|
||||||
parser.add_argument("username", help="filter users by username", nargs="?")
|
parser.add_argument("username", help="filter users by username", nargs="?")
|
||||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||||
parser.add_argument("-r", "--role", help="filter users by role", type=UserAccess, choices=UserAccess)
|
parser.add_argument("-r", "--role", help="filter users by role", type=UserAccess, choices=enum_values(UserAccess))
|
||||||
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec
|
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec
|
||||||
password="", quiet=True, unsafe=True)
|
password="", quiet=True, unsafe=True)
|
||||||
return parser
|
return parser
|
||||||
@ -688,6 +749,23 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _set_version_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
add parser for version subcommand
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root(SubParserAction): subparsers for the commands
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
argparse.ArgumentParser: created argument parser
|
||||||
|
"""
|
||||||
|
parser = root.add_parser("version", help="application version",
|
||||||
|
description="print application and its dependencies versions", formatter_class=_formatter)
|
||||||
|
parser.set_defaults(handler=handlers.Versions, architecture=[""], lock=None, no_report=True, quiet=True,
|
||||||
|
unsafe=True)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
"""
|
"""
|
||||||
add parser for web subcommand
|
add parser for web subcommand
|
||||||
|
@ -21,11 +21,12 @@ import requests
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Any, Iterable, Set
|
from typing import Any, Iterable, Set
|
||||||
|
|
||||||
from ahriman.application.application.application_properties import ApplicationProperties
|
from ahriman.application.application.application_properties import ApplicationProperties
|
||||||
from ahriman.core.build_tools.sources import Sources
|
from ahriman.core.build_tools.sources import Sources
|
||||||
from ahriman.core.util import package_like, tmpdir
|
from ahriman.core.util import package_like
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.package_source import PackageSource
|
from ahriman.models.package_source import PackageSource
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
@ -85,7 +86,8 @@ class ApplicationPackages(ApplicationProperties):
|
|||||||
self.database.build_queue_insert(package)
|
self.database.build_queue_insert(package)
|
||||||
self.database.remote_update(package)
|
self.database.remote_update(package)
|
||||||
|
|
||||||
with tmpdir() as local_dir:
|
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, \
|
||||||
|
(local_dir := Path(dir_name)): # pylint: disable=confusing-with-statement
|
||||||
Sources.load(local_dir, package, self.database.patches_get(package.base), self.repository.paths)
|
Sources.load(local_dir, package, self.database.patches_get(package.base), self.repository.paths)
|
||||||
self._process_dependencies(local_dir, known_packages, without_dependencies)
|
self._process_dependencies(local_dir, known_packages, without_dependencies)
|
||||||
|
|
||||||
@ -127,7 +129,7 @@ class ApplicationPackages(ApplicationProperties):
|
|||||||
source(str): remote URL of the package archive
|
source(str): remote URL of the package archive
|
||||||
"""
|
"""
|
||||||
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
|
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
|
||||||
response = requests.get(source, stream=True)
|
response = requests.get(source, stream=True, timeout=None) # timeout=None to suppress pylint warns
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
with dst.open("wb") as local_file:
|
with dst.open("wb") as local_file:
|
||||||
|
@ -17,14 +17,13 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.repository import Repository
|
from ahriman.core.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
class ApplicationProperties:
|
class ApplicationProperties(LazyLogging):
|
||||||
"""
|
"""
|
||||||
application base properties class
|
application base properties class
|
||||||
|
|
||||||
@ -32,7 +31,6 @@ class ApplicationProperties:
|
|||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
database(SQLite): database instance
|
database(SQLite): database instance
|
||||||
logger(logging.Logger): application logger
|
|
||||||
repository(Repository): repository instance
|
repository(Repository): repository instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -46,7 +44,6 @@ class ApplicationProperties:
|
|||||||
no_report(bool): force disable reporting
|
no_report(bool): force disable reporting
|
||||||
unsafe(bool): if set no user check will be performed before path creation
|
unsafe(bool): if set no user check will be performed before path creation
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("root")
|
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.database = SQLite.load(configuration)
|
self.database = SQLite.load(configuration)
|
||||||
|
@ -32,6 +32,7 @@ from ahriman.application.handlers.remove_unknown import RemoveUnknown
|
|||||||
from ahriman.application.handlers.restore import Restore
|
from ahriman.application.handlers.restore import Restore
|
||||||
from ahriman.application.handlers.search import Search
|
from ahriman.application.handlers.search import Search
|
||||||
from ahriman.application.handlers.setup import Setup
|
from ahriman.application.handlers.setup import Setup
|
||||||
|
from ahriman.application.handlers.shell import Shell
|
||||||
from ahriman.application.handlers.sign import Sign
|
from ahriman.application.handlers.sign import Sign
|
||||||
from ahriman.application.handlers.status import Status
|
from ahriman.application.handlers.status import Status
|
||||||
from ahriman.application.handlers.status_update import StatusUpdate
|
from ahriman.application.handlers.status_update import StatusUpdate
|
||||||
@ -39,4 +40,5 @@ from ahriman.application.handlers.triggers import Triggers
|
|||||||
from ahriman.application.handlers.unsafe_commands import UnsafeCommands
|
from ahriman.application.handlers.unsafe_commands import UnsafeCommands
|
||||||
from ahriman.application.handlers.update import Update
|
from ahriman.application.handlers.update import Update
|
||||||
from ahriman.application.handlers.users import Users
|
from ahriman.application.handlers.users import Users
|
||||||
|
from ahriman.application.handlers.versions import Versions
|
||||||
from ahriman.application.handlers.web import Web
|
from ahriman.application.handlers.web import Web
|
||||||
|
@ -52,7 +52,7 @@ class Rebuild(Handler):
|
|||||||
if args.from_database:
|
if args.from_database:
|
||||||
updates = Rebuild.extract_packages(application)
|
updates = Rebuild.extract_packages(application)
|
||||||
else:
|
else:
|
||||||
updates = application.repository.packages_depends_on(depends_on)
|
updates = application.repository.packages_depend_on(depends_on)
|
||||||
|
|
||||||
Rebuild.check_if_empty(args.exit_code, not updates)
|
Rebuild.check_if_empty(args.exit_code, not updates)
|
||||||
if args.dry_run:
|
if args.dry_run:
|
||||||
|
59
src/ahriman/application/handlers/shell.py
Normal file
59
src/ahriman/application/handlers/shell.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2022 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import argparse
|
||||||
|
import code
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
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 StringPrinter
|
||||||
|
|
||||||
|
|
||||||
|
class Shell(Handler):
|
||||||
|
"""
|
||||||
|
python shell handler
|
||||||
|
"""
|
||||||
|
|
||||||
|
ALLOW_MULTI_ARCHITECTURE_RUN = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
|
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
|
||||||
|
"""
|
||||||
|
callback for command line
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args(argparse.Namespace): command line args
|
||||||
|
architecture(str): repository architecture
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
no_report(bool): force disable reporting
|
||||||
|
unsafe(bool): if set no user check will be performed before path creation
|
||||||
|
"""
|
||||||
|
# pylint: disable=possibly-unused-variable
|
||||||
|
application = Application(architecture, configuration, no_report, unsafe)
|
||||||
|
if args.verbose:
|
||||||
|
# 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)
|
||||||
|
code.interact(local=locals())
|
@ -52,11 +52,11 @@ class Status(Handler):
|
|||||||
# we are using reporter here
|
# we are using reporter here
|
||||||
client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter
|
client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter
|
||||||
if args.ahriman:
|
if args.ahriman:
|
||||||
ahriman = client.get_self()
|
service_status = client.get_internal()
|
||||||
StatusPrinter(ahriman).print(args.info)
|
StatusPrinter(service_status.status).print(args.info)
|
||||||
if args.package:
|
if args.package:
|
||||||
packages: Iterable[Tuple[Package, BuildStatus]] = sum(
|
packages: Iterable[Tuple[Package, BuildStatus]] = sum(
|
||||||
[client.get(base) for base in args.package],
|
(client.get(base) for base in args.package),
|
||||||
start=[])
|
start=[])
|
||||||
else:
|
else:
|
||||||
packages = client.get(None)
|
packages = client.get(None)
|
||||||
|
@ -45,4 +45,8 @@ class Triggers(Handler):
|
|||||||
no_report(bool): force disable reporting
|
no_report(bool): force disable reporting
|
||||||
unsafe(bool): if set no user check will be performed before path creation
|
unsafe(bool): if set no user check will be performed before path creation
|
||||||
"""
|
"""
|
||||||
Application(architecture, configuration, no_report, unsafe).repository.process_triggers(Result())
|
application = Application(architecture, configuration, no_report, unsafe)
|
||||||
|
if args.trigger:
|
||||||
|
loader = application.repository.triggers
|
||||||
|
loader.triggers = [loader.load_trigger(trigger) for trigger in args.trigger]
|
||||||
|
application.repository.process_triggers(Result())
|
||||||
|
@ -149,7 +149,7 @@ class Users(Handler):
|
|||||||
Returns:
|
Returns:
|
||||||
User: built user descriptor
|
User: built user descriptor
|
||||||
"""
|
"""
|
||||||
user = User(args.username, args.password, args.role)
|
password = args.password
|
||||||
if user.password is None:
|
if password is None:
|
||||||
user.password = getpass.getpass()
|
password = getpass.getpass()
|
||||||
return user
|
return User(username=args.username, password=password, access=args.role)
|
||||||
|
87
src/ahriman/application/handlers/versions.py
Normal file
87
src/ahriman/application/handlers/versions.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2022 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import argparse
|
||||||
|
import pkg_resources
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple, Type
|
||||||
|
|
||||||
|
from ahriman import version
|
||||||
|
from ahriman.application.handlers import Handler
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.formatters import VersionPrinter
|
||||||
|
|
||||||
|
|
||||||
|
class Versions(Handler):
|
||||||
|
"""
|
||||||
|
version handler
|
||||||
|
"""
|
||||||
|
|
||||||
|
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||||
|
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
|
||||||
|
"""
|
||||||
|
callback for command line
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args(argparse.Namespace): command line args
|
||||||
|
architecture(str): repository architecture
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
no_report(bool): force disable reporting
|
||||||
|
unsafe(bool): if set no user check will be performed before path creation
|
||||||
|
"""
|
||||||
|
VersionPrinter(f"Module version {version.__version__}",
|
||||||
|
{"Python": sys.version}).print(verbose=False, separator=" ")
|
||||||
|
packages = Versions.package_dependencies("ahriman", ("pacman", "s3", "web"))
|
||||||
|
VersionPrinter("Installed packages", packages).print(verbose=False, separator=" ")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def package_dependencies(root: str, root_extras: Tuple[str, ...] = ()) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
extract list of ahriman package dependencies installed into system with their versions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root(str): root package name
|
||||||
|
root_extras(Tuple[str, ...]): extras for the root package (Default value = ())
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, str]: map of installed dependency to its version
|
||||||
|
"""
|
||||||
|
resources: Dict[str, pkg_resources.Distribution] = pkg_resources.working_set.by_key # type: ignore
|
||||||
|
|
||||||
|
def dependencies_by_key(key: str, extras: Tuple[str, ...] = ()) -> List[str]:
|
||||||
|
return [entry.key for entry in resources[key].requires(extras)]
|
||||||
|
|
||||||
|
keys: List[str] = []
|
||||||
|
portion = {key for key in dependencies_by_key(root, root_extras) if key in resources}
|
||||||
|
while portion:
|
||||||
|
keys.extend(portion)
|
||||||
|
portion = {
|
||||||
|
key
|
||||||
|
for key in sum((dependencies_by_key(key) for key in portion), start=[])
|
||||||
|
if key not in keys and key in resources
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
resource.project_name: resource.version
|
||||||
|
for resource in map(lambda key: resources[key], keys)
|
||||||
|
}
|
@ -20,7 +20,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
@ -29,12 +28,13 @@ from typing import Literal, Optional, Type
|
|||||||
from ahriman import version
|
from ahriman import version
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import DuplicateRun
|
from ahriman.core.exceptions import DuplicateRun
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.status.client import Client
|
from ahriman.core.status.client import Client
|
||||||
from ahriman.core.util import check_user
|
from ahriman.core.util import check_user
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
|
|
||||||
|
|
||||||
class Lock:
|
class Lock(LazyLogging):
|
||||||
"""
|
"""
|
||||||
wrapper for application lock file
|
wrapper for application lock file
|
||||||
|
|
||||||
@ -115,10 +115,8 @@ class Lock:
|
|||||||
"""
|
"""
|
||||||
status = self.reporter.get_internal()
|
status = self.reporter.get_internal()
|
||||||
if status.version is not None and status.version != version.__version__:
|
if status.version is not None and status.version != version.__version__:
|
||||||
logging.getLogger("root").warning(
|
self.logger.warning("status watcher version mismatch, our %s, their %s",
|
||||||
"status watcher version mismatch, our %s, their %s",
|
version.__version__, status.version)
|
||||||
version.__version__,
|
|
||||||
status.version)
|
|
||||||
|
|
||||||
def check_user(self) -> None:
|
def check_user(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -36,11 +36,13 @@ class AUR(Remote):
|
|||||||
DEFAULT_AUR_URL(str): (class attribute) default AUR url
|
DEFAULT_AUR_URL(str): (class attribute) default AUR url
|
||||||
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
|
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
|
||||||
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
|
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
|
||||||
|
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_AUR_URL = "https://aur.archlinux.org"
|
DEFAULT_AUR_URL = "https://aur.archlinux.org"
|
||||||
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc"
|
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc"
|
||||||
DEFAULT_RPC_VERSION = "5"
|
DEFAULT_RPC_VERSION = "5"
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
|
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
|
||||||
@ -113,7 +115,7 @@ class AUR(Remote):
|
|||||||
query[key] = value
|
query[key] = value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(self.DEFAULT_RPC_URL, params=query)
|
response = requests.get(self.DEFAULT_RPC_URL, params=query, timeout=self.DEFAULT_TIMEOUT)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return self.parse_response(response.json())
|
return self.parse_response(response.json())
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
|
@ -36,11 +36,13 @@ class Official(Remote):
|
|||||||
DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url
|
DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url
|
||||||
DEFAULT_SEARCH_REPOSITORIES(List[str]): (class attribute) default list of repositories to search
|
DEFAULT_SEARCH_REPOSITORIES(List[str]): (class attribute) default list of repositories to search
|
||||||
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
|
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
|
||||||
|
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
|
DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
|
||||||
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib", "Community"]
|
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib", "Community"]
|
||||||
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
|
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
|
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
|
||||||
@ -101,7 +103,10 @@ class Official(Remote):
|
|||||||
List[AURPackage]: response parsed to package list
|
List[AURPackage]: response parsed to package list
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = requests.get(self.DEFAULT_RPC_URL, params={by: args, "repo": self.DEFAULT_SEARCH_REPOSITORIES})
|
response = requests.get(
|
||||||
|
self.DEFAULT_RPC_URL,
|
||||||
|
params={by: args, "repo": self.DEFAULT_SEARCH_REPOSITORIES},
|
||||||
|
timeout=self.DEFAULT_TIMEOUT)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return self.parse_response(response.json())
|
return self.parse_response(response.json())
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
|
@ -19,21 +19,17 @@
|
|||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import Dict, List, Type
|
from typing import Dict, List, Type
|
||||||
|
|
||||||
from ahriman.core.alpm.pacman import Pacman
|
from ahriman.core.alpm.pacman import Pacman
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.models.aur_package import AURPackage
|
from ahriman.models.aur_package import AURPackage
|
||||||
|
|
||||||
|
|
||||||
class Remote:
|
class Remote(LazyLogging):
|
||||||
"""
|
"""
|
||||||
base class for remote package search
|
base class for remote package search
|
||||||
|
|
||||||
Attributes:
|
|
||||||
logger(logging.Logger): class logger
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
These classes are designed to be used without instancing. In order to achieve it several class methods are
|
These classes are designed to be used without instancing. In order to achieve it several class methods are
|
||||||
provided: ``info``, ``multisearch`` and ``search``. Thus, the basic flow is the following::
|
provided: ``info``, ``multisearch`` and ``search``. Thus, the basic flow is the following::
|
||||||
@ -47,12 +43,6 @@ class Remote:
|
|||||||
directly, whereas ``multisearch`` splits search one by one and finds intersection between search results.
|
directly, whereas ``multisearch`` splits search one by one and finds intersection between search results.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""
|
|
||||||
default constructor
|
|
||||||
"""
|
|
||||||
self.logger = logging.getLogger("build_details")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def info(cls: Type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage:
|
def info(cls: Type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||||
"""
|
"""
|
||||||
|
@ -17,22 +17,20 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ahriman.core.exceptions import BuildFailed
|
from ahriman.core.exceptions import BuildFailed
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.util import check_output
|
from ahriman.core.util import check_output
|
||||||
from ahriman.models.repository_paths import RepositoryPaths
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
|
|
||||||
class Repo:
|
class Repo(LazyLogging):
|
||||||
"""
|
"""
|
||||||
repo-add and repo-remove wrapper
|
repo-add and repo-remove wrapper
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
logger(logging.Logger): class logger
|
|
||||||
name(str): repository name
|
name(str): repository name
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
sign_args(List[str]): additional args which have to be used to sign repository archive
|
sign_args(List[str]): additional args which have to be used to sign repository archive
|
||||||
@ -50,7 +48,6 @@ class Repo:
|
|||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
sign_args(List[str]): additional args which have to be used to sign repository archive
|
sign_args(List[str]): additional args which have to be used to sign repository archive
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("build_details")
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.paths = paths
|
self.paths = paths
|
||||||
self.uid, _ = paths.root_owner
|
self.uid, _ = paths.root_owner
|
||||||
|
@ -19,25 +19,23 @@
|
|||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import Optional, Type
|
from typing import Optional, Type
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.models.auth_settings import AuthSettings
|
from ahriman.models.auth_settings import AuthSettings
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
|
|
||||||
|
|
||||||
class Auth:
|
class Auth(LazyLogging):
|
||||||
"""
|
"""
|
||||||
helper to deal with user authorization
|
helper to deal with user authorization
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
enabled(bool): indicates if authorization is enabled
|
enabled(bool): indicates if authorization is enabled
|
||||||
logger(logging.Logger): class logger
|
|
||||||
max_age(int): session age in seconds. It will be used for both client side and server side checks
|
max_age(int): session age in seconds. It will be used for both client side and server side checks
|
||||||
safe_build_status(bool): allow read only access to the index page
|
allow_read_only(bool): allow read only access to APIs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
|
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
|
||||||
@ -48,9 +46,7 @@ class Auth:
|
|||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.Disabled)
|
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.Disabled)
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("http")
|
self.allow_read_only = configuration.getboolean("auth", "allow_read_only")
|
||||||
|
|
||||||
self.safe_build_status = configuration.getboolean("auth", "safe_build_status")
|
|
||||||
|
|
||||||
self.enabled = provider.is_enabled
|
self.enabled = provider.is_enabled
|
||||||
self.max_age = configuration.getint("auth", "max_age", fallback=7 * 24 * 3600)
|
self.max_age = configuration.getint("auth", "max_age", fallback=7 * 24 * 3600)
|
||||||
@ -89,7 +85,7 @@ class Auth:
|
|||||||
return OAuth(configuration, database)
|
return OAuth(configuration, database)
|
||||||
return cls(configuration)
|
return cls(configuration)
|
||||||
|
|
||||||
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool: # pylint: disable=no-self-use
|
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool:
|
||||||
"""
|
"""
|
||||||
validate user password
|
validate user password
|
||||||
|
|
||||||
@ -103,7 +99,7 @@ class Auth:
|
|||||||
del username, password
|
del username, password
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def known_username(self, username: Optional[str]) -> bool: # pylint: disable=no-self-use
|
async def known_username(self, username: Optional[str]) -> bool:
|
||||||
"""
|
"""
|
||||||
check if user is known
|
check if user is known
|
||||||
|
|
||||||
@ -116,7 +112,7 @@ class Auth:
|
|||||||
del username
|
del username
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool: # pylint: disable=no-self-use
|
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool:
|
||||||
"""
|
"""
|
||||||
validate if user has access to requested resource
|
validate if user has access to requested resource
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class OAuth(Mapping):
|
|||||||
self.client_secret = configuration.get("auth", "client_secret")
|
self.client_secret = configuration.get("auth", "client_secret")
|
||||||
# in order to use OAuth feature the service must be publicity available
|
# in order to use OAuth feature the service must be publicity available
|
||||||
# thus we expect that address is set
|
# thus we expect that address is set
|
||||||
self.redirect_uri = f"""{configuration.get("web", "address")}/user-api/v1/login"""
|
self.redirect_uri = f"""{configuration.get("web", "address")}/api/v1/login"""
|
||||||
self.provider = self.get_provider(configuration.get("auth", "oauth_provider"))
|
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")
|
self.scopes = configuration.get("auth", "oauth_scopes")
|
||||||
@ -69,7 +69,7 @@ class OAuth(Mapping):
|
|||||||
Returns:
|
Returns:
|
||||||
str: login control as html code to insert
|
str: login control as html code to insert
|
||||||
"""
|
"""
|
||||||
return """<a class="nav-link" href="/user-api/v1/login" title="login via OAuth2">login</a>"""
|
return """<a class="nav-link" href="/api/v1/login" title="login via OAuth2">login</a>"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_provider(name: str) -> Type[aioauth_client.OAuth2Client]:
|
def get_provider(name: str) -> Type[aioauth_client.OAuth2Client]:
|
||||||
|
@ -17,81 +17,51 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.util import check_output, walk
|
from ahriman.core.util import check_output, walk
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||||
from ahriman.models.remote_source import RemoteSource
|
from ahriman.models.remote_source import RemoteSource
|
||||||
from ahriman.models.repository_paths import RepositoryPaths
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
|
|
||||||
class Sources:
|
class Sources(LazyLogging):
|
||||||
"""
|
"""
|
||||||
helper to download package sources (PKGBUILD etc)
|
helper to download package sources (PKGBUILD etc)
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
|
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
|
||||||
Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages
|
Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages
|
||||||
logger(logging.Logger): (class attribute) class logger
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_BRANCH = "master" # default fallback branch
|
DEFAULT_BRANCH = "master" # default fallback branch
|
||||||
logger = logging.getLogger("build_details")
|
|
||||||
|
|
||||||
_check_output = check_output
|
_check_output = check_output
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _add(sources_dir: Path, *pattern: str) -> None:
|
def extend_architectures(sources_dir: Path, architecture: str) -> None:
|
||||||
"""
|
"""
|
||||||
track found files via git
|
extend existing PKGBUILD with repository architecture
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sources_dir(Path): local path to git repository
|
sources_dir(Path): local path to directory with source files
|
||||||
*pattern(str): glob patterns
|
architecture(str): repository architecture
|
||||||
"""
|
"""
|
||||||
# glob directory to find files which match the specified patterns
|
pkgbuild_path = sources_dir / "PKGBUILD"
|
||||||
found_files: List[Path] = []
|
if not pkgbuild_path.is_file():
|
||||||
for glob in pattern:
|
return
|
||||||
found_files.extend(sources_dir.glob(glob))
|
|
||||||
if not found_files:
|
|
||||||
return # no additional files found
|
|
||||||
Sources.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],
|
|
||||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
|
||||||
|
|
||||||
@staticmethod
|
architectures = Package.supported_architectures(sources_dir)
|
||||||
def _diff(sources_dir: Path) -> str:
|
if "any" in architectures: # makepkg does not like when there is any other arch except for any
|
||||||
"""
|
return
|
||||||
generate diff from the current version and write it to the output file
|
architectures.add(architecture)
|
||||||
|
patch = PkgbuildPatch("arch", list(architectures))
|
||||||
Args:
|
patch.write(pkgbuild_path)
|
||||||
sources_dir(Path): local path to git repository
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: patch as plain string
|
|
||||||
"""
|
|
||||||
return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _move(pkgbuild_dir: Path, sources_dir: Path) -> None:
|
|
||||||
"""
|
|
||||||
move content from pkgbuild_dir to sources_dir
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
|
|
||||||
sources_dir(Path): path to target directory
|
|
||||||
"""
|
|
||||||
if pkgbuild_dir == sources_dir:
|
|
||||||
return # directories are the same, no need to move
|
|
||||||
for src in walk(pkgbuild_dir):
|
|
||||||
dst = sources_dir / src.relative_to(pkgbuild_dir)
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
|
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
|
||||||
@ -102,37 +72,38 @@ class Sources:
|
|||||||
sources_dir(Path): local path to fetch
|
sources_dir(Path): local path to fetch
|
||||||
remote(Optional[RemoteSource]): remote target (from where to fetch)
|
remote(Optional[RemoteSource]): remote target (from where to fetch)
|
||||||
"""
|
"""
|
||||||
|
instance = Sources()
|
||||||
# local directory exists and there is .git directory
|
# local directory exists and there is .git directory
|
||||||
is_initialized_git = (sources_dir / ".git").is_dir()
|
is_initialized_git = (sources_dir / ".git").is_dir()
|
||||||
if is_initialized_git and not Sources.has_remotes(sources_dir):
|
if is_initialized_git and not instance.has_remotes(sources_dir):
|
||||||
# there is git repository, but no remote configured so far
|
# there is git repository, but no remote configured so far
|
||||||
Sources.logger.info("skip update at %s because there are no branches configured", sources_dir)
|
instance.logger.info("skip update at %s because there are no branches configured", sources_dir)
|
||||||
return
|
return
|
||||||
|
|
||||||
branch = remote.branch if remote is not None else Sources.DEFAULT_BRANCH
|
branch = remote.branch if remote is not None else instance.DEFAULT_BRANCH
|
||||||
if is_initialized_git:
|
if is_initialized_git:
|
||||||
Sources.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
|
instance.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
|
||||||
Sources._check_output("git", "fetch", "origin", branch,
|
Sources._check_output("git", "fetch", "origin", branch,
|
||||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
exception=None, cwd=sources_dir, logger=instance.logger)
|
||||||
elif remote is not None:
|
elif remote is not None:
|
||||||
Sources.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
|
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",
|
Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
|
||||||
remote.git_url, str(sources_dir),
|
remote.git_url, str(sources_dir),
|
||||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
exception=None, cwd=sources_dir, logger=instance.logger)
|
||||||
else:
|
else:
|
||||||
# it will cause an exception later
|
# it will cause an exception later
|
||||||
Sources.logger.error("%s is not initialized, but no remote provided", sources_dir)
|
instance.logger.error("%s is not initialized, but no remote provided", sources_dir)
|
||||||
|
|
||||||
# and now force reset to our branch
|
# and now force reset to our branch
|
||||||
Sources._check_output("git", "checkout", "--force", branch,
|
Sources._check_output("git", "checkout", "--force", branch,
|
||||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
exception=None, cwd=sources_dir, logger=instance.logger)
|
||||||
Sources._check_output("git", "reset", "--hard", f"origin/{branch}",
|
Sources._check_output("git", "reset", "--hard", f"origin/{branch}",
|
||||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
exception=None, cwd=sources_dir, logger=instance.logger)
|
||||||
|
|
||||||
# move content if required
|
# move content if required
|
||||||
# we are using full path to source directory in order to make append possible
|
# we are using full path to source directory in order to make append possible
|
||||||
pkgbuild_dir = remote.pkgbuild_dir if remote is not None else sources_dir.resolve()
|
pkgbuild_dir = remote.pkgbuild_dir if remote is not None else sources_dir.resolve()
|
||||||
Sources._move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
|
instance.move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_remotes(sources_dir: Path) -> bool:
|
def has_remotes(sources_dir: Path) -> bool:
|
||||||
@ -145,7 +116,8 @@ class Sources:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True in case if there is any remote and false otherwise
|
bool: True in case if there is any remote and false otherwise
|
||||||
"""
|
"""
|
||||||
remotes = Sources._check_output("git", "remote", exception=None, cwd=sources_dir, logger=Sources.logger)
|
instance = Sources()
|
||||||
|
remotes = Sources._check_output("git", "remote", exception=None, cwd=sources_dir, logger=instance.logger)
|
||||||
return bool(remotes)
|
return bool(remotes)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -156,8 +128,9 @@ class Sources:
|
|||||||
Args:
|
Args:
|
||||||
sources_dir(Path): local path to sources
|
sources_dir(Path): local path to sources
|
||||||
"""
|
"""
|
||||||
Sources._check_output("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH,
|
instance = Sources()
|
||||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
Sources._check_output("git", "init", "--initial-branch", instance.DEFAULT_BRANCH,
|
||||||
|
exception=None, cwd=sources_dir, logger=instance.logger)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(sources_dir: Path, package: Package, patch: Optional[str], paths: RepositoryPaths) -> None:
|
def load(sources_dir: Path, package: Package, patch: Optional[str], paths: RepositoryPaths) -> None:
|
||||||
@ -170,29 +143,15 @@ class Sources:
|
|||||||
patch(Optional[str]): optional patch to be applied
|
patch(Optional[str]): optional patch to be applied
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
"""
|
"""
|
||||||
|
instance = Sources()
|
||||||
if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir:
|
if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir:
|
||||||
# no need to clone whole repository, just copy from cache first
|
# no need to clone whole repository, just copy from cache first
|
||||||
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
|
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
|
||||||
Sources.fetch(sources_dir, package.remote)
|
instance.fetch(sources_dir, package.remote)
|
||||||
|
|
||||||
if patch is None:
|
if patch is not None:
|
||||||
Sources.logger.info("no patches found")
|
instance.patch_apply(sources_dir, patch)
|
||||||
return
|
instance.extend_architectures(sources_dir, paths.architecture)
|
||||||
Sources.patch_apply(sources_dir, patch)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def patch_apply(sources_dir: Path, patch: str) -> None:
|
|
||||||
"""
|
|
||||||
apply patches if any
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sources_dir(Path): local path to directory with git sources
|
|
||||||
patch(str): patch to be applied
|
|
||||||
"""
|
|
||||||
# create patch
|
|
||||||
Sources.logger.info("apply patch from database")
|
|
||||||
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
|
||||||
exception=None, cwd=sources_dir, input_data=patch, logger=Sources.logger)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def patch_create(sources_dir: Path, *pattern: str) -> str:
|
def patch_create(sources_dir: Path, *pattern: str) -> str:
|
||||||
@ -206,6 +165,67 @@ class Sources:
|
|||||||
Returns:
|
Returns:
|
||||||
str: patch as plain text
|
str: patch as plain text
|
||||||
"""
|
"""
|
||||||
Sources._add(sources_dir, *pattern)
|
instance = Sources()
|
||||||
diff = Sources._diff(sources_dir)
|
instance.add(sources_dir, *pattern)
|
||||||
|
diff = instance.diff(sources_dir)
|
||||||
return f"{diff}\n" # otherwise, patch will be broken
|
return f"{diff}\n" # otherwise, patch will be broken
|
||||||
|
|
||||||
|
def add(self, sources_dir: Path, *pattern: str) -> None:
|
||||||
|
"""
|
||||||
|
track found files via git
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sources_dir(Path): local path to git repository
|
||||||
|
*pattern(str): glob patterns
|
||||||
|
"""
|
||||||
|
# glob directory to find files which match the specified patterns
|
||||||
|
found_files: List[Path] = []
|
||||||
|
for glob in pattern:
|
||||||
|
found_files.extend(sources_dir.glob(glob))
|
||||||
|
if not found_files:
|
||||||
|
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],
|
||||||
|
exception=None, cwd=sources_dir, logger=self.logger)
|
||||||
|
|
||||||
|
def diff(self, sources_dir: Path) -> str:
|
||||||
|
"""
|
||||||
|
generate diff from the current version and write it to the output file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sources_dir(Path): local path to git repository
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: patch as plain string
|
||||||
|
"""
|
||||||
|
return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=self.logger)
|
||||||
|
|
||||||
|
def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None:
|
||||||
|
"""
|
||||||
|
move content from pkgbuild_dir to sources_dir
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
|
||||||
|
sources_dir(Path): path to target directory
|
||||||
|
"""
|
||||||
|
del self
|
||||||
|
if pkgbuild_dir == sources_dir:
|
||||||
|
return # directories are the same, no need to move
|
||||||
|
for src in walk(pkgbuild_dir):
|
||||||
|
dst = sources_dir / src.relative_to(pkgbuild_dir)
|
||||||
|
shutil.move(src, dst)
|
||||||
|
|
||||||
|
def patch_apply(self, sources_dir: Path, patch: str) -> None:
|
||||||
|
"""
|
||||||
|
apply patches if any
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sources_dir(Path): local path to directory with git sources
|
||||||
|
patch(str): patch to be applied
|
||||||
|
"""
|
||||||
|
# create patch
|
||||||
|
self.logger.info("apply patch from database")
|
||||||
|
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||||
|
exception=None, cwd=sources_dir, input_data=patch, logger=self.logger)
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -26,18 +24,17 @@ from ahriman.core.build_tools.sources import Sources
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.core.exceptions import BuildFailed
|
from ahriman.core.exceptions import BuildFailed
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.util import check_output
|
from ahriman.core.util import check_output
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.repository_paths import RepositoryPaths
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
|
|
||||||
class Task:
|
class Task(LazyLogging):
|
||||||
"""
|
"""
|
||||||
base package build task
|
base package build task
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
build_logger(logging.Logger): logger for build process
|
|
||||||
logger(logging.Logger): class logger
|
|
||||||
package(Package): package definitions
|
package(Package): package definitions
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
uid(int): uid of the repository owner user
|
uid(int): uid of the repository owner user
|
||||||
@ -54,8 +51,6 @@ class Task:
|
|||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("root")
|
|
||||||
self.build_logger = logging.getLogger("build_details")
|
|
||||||
self.package = package
|
self.package = package
|
||||||
self.paths = paths
|
self.paths = paths
|
||||||
self.uid, _ = paths.root_owner
|
self.uid, _ = paths.root_owner
|
||||||
@ -85,14 +80,14 @@ class Task:
|
|||||||
*command,
|
*command,
|
||||||
exception=BuildFailed(self.package.base),
|
exception=BuildFailed(self.package.base),
|
||||||
cwd=sources_dir,
|
cwd=sources_dir,
|
||||||
logger=self.build_logger,
|
logger=self.logger,
|
||||||
user=self.uid)
|
user=self.uid)
|
||||||
|
|
||||||
# well it is not actually correct, but we can deal with it
|
# well it is not actually correct, but we can deal with it
|
||||||
packages = Task._check_output("makepkg", "--packagelist",
|
packages = Task._check_output("makepkg", "--packagelist",
|
||||||
exception=BuildFailed(self.package.base),
|
exception=BuildFailed(self.package.base),
|
||||||
cwd=sources_dir,
|
cwd=sources_dir,
|
||||||
logger=self.build_logger).splitlines()
|
logger=self.logger).splitlines()
|
||||||
return [Path(package) for package in packages]
|
return [Path(package) for package in packages]
|
||||||
|
|
||||||
def init(self, sources_dir: Path, database: SQLite) -> None:
|
def init(self, sources_dir: Path, database: SQLite) -> None:
|
||||||
|
@ -220,7 +220,7 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# pylint and mypy are too stupid to find these methods
|
# pylint and mypy are too stupid to find these methods
|
||||||
# pylint: disable=missing-function-docstring,multiple-statements,unused-argument,no-self-use
|
# pylint: disable=missing-function-docstring,multiple-statements,unused-argument
|
||||||
def getlist(self, *args: Any, **kwargs: Any) -> List[str]: ...
|
def getlist(self, *args: Any, **kwargs: Any) -> List[str]: ...
|
||||||
|
|
||||||
def getpath(self, *args: Any, **kwargs: Any) -> Path: ...
|
def getpath(self, *args: Any, **kwargs: Any) -> Path: ...
|
||||||
|
@ -36,7 +36,7 @@ def migrate_package_remotes(connection: Connection, paths: RepositoryPaths) -> N
|
|||||||
connection(Connection): database connection
|
connection(Connection): database connection
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
"""
|
"""
|
||||||
from ahriman.core.database.operations.package_operations import PackageOperations
|
from ahriman.core.database.operations import PackageOperations
|
||||||
|
|
||||||
def insert_remote(base: str, remote: RemoteSource) -> None:
|
def insert_remote(base: str, remote: RemoteSource) -> None:
|
||||||
connection.execute(
|
connection.execute(
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
@ -29,11 +27,12 @@ from typing import List, Type
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database.data import migrate_data
|
from ahriman.core.database.data import migrate_data
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.models.migration import Migration
|
from ahriman.models.migration import Migration
|
||||||
from ahriman.models.migration_result import MigrationResult
|
from ahriman.models.migration_result import MigrationResult
|
||||||
|
|
||||||
|
|
||||||
class Migrations:
|
class Migrations(LazyLogging):
|
||||||
"""
|
"""
|
||||||
simple migration wrapper for the sqlite
|
simple migration wrapper for the sqlite
|
||||||
idea comes from https://www.ash.dev/blog/simple-migration-system-in-sqlite/
|
idea comes from https://www.ash.dev/blog/simple-migration-system-in-sqlite/
|
||||||
@ -41,7 +40,6 @@ class Migrations:
|
|||||||
Attributes:
|
Attributes:
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
connection(Connection): database connection
|
connection(Connection): database connection
|
||||||
logger(logging.Logger): class logger
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, connection: Connection, configuration: Configuration) -> None:
|
def __init__(self, connection: Connection, configuration: Configuration) -> None:
|
||||||
@ -54,7 +52,6 @@ class Migrations:
|
|||||||
"""
|
"""
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.logger = logging.getLogger("database")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def migrate(cls: Type[Migrations], connection: Connection, configuration: Configuration) -> MigrationResult:
|
def migrate(cls: Type[Migrations], connection: Connection, configuration: Configuration) -> MigrationResult:
|
||||||
@ -86,7 +83,7 @@ class Migrations:
|
|||||||
module = import_module(f"{__name__}.{module_name}")
|
module = import_module(f"{__name__}.{module_name}")
|
||||||
steps: List[str] = getattr(module, "steps", [])
|
steps: List[str] = getattr(module, "steps", [])
|
||||||
self.logger.debug("found migration %s at index %s with steps count %s", module_name, index, len(steps))
|
self.logger.debug("found migration %s at index %s with steps count %s", module_name, index, len(steps))
|
||||||
migrations.append(Migration(index, module_name, steps))
|
migrations.append(Migration(index=index, name=module_name, steps=steps))
|
||||||
|
|
||||||
return migrations
|
return migrations
|
||||||
|
|
||||||
@ -100,7 +97,7 @@ class Migrations:
|
|||||||
migrations = self.migrations()
|
migrations = self.migrations()
|
||||||
current_version = self.user_version()
|
current_version = self.user_version()
|
||||||
expected_version = len(migrations)
|
expected_version = len(migrations)
|
||||||
result = MigrationResult(current_version, expected_version)
|
result = MigrationResult(old_version=current_version, new_version=expected_version)
|
||||||
|
|
||||||
if not result.is_outdated:
|
if not result.is_outdated:
|
||||||
self.logger.info("no migrations required")
|
self.logger.info("no migrations required")
|
||||||
|
33
src/ahriman/core/database/migrations/m002_user_access.py
Normal file
33
src/ahriman/core/database/migrations/m002_user_access.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#
|
||||||
|
# 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 = [
|
||||||
|
"""
|
||||||
|
update users set access = 'read' where access = 'safe'
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
update users set access = 'reporter' where access = 'read'
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
update users set access = 'full' where access = 'write'
|
||||||
|
""",
|
||||||
|
]
|
@ -17,3 +17,9 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
from 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.package_operations import PackageOperations
|
||||||
|
from ahriman.core.database.operations.patch_operations import PatchOperations
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
from sqlite3 import Connection
|
from sqlite3 import Connection
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from ahriman.core.database.operations.operations import Operations
|
from ahriman.core.database.operations import Operations
|
||||||
from ahriman.models.user import User
|
from ahriman.models.user import User
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class AuthOperations(Operations):
|
|||||||
|
|
||||||
def run(connection: Connection) -> List[User]:
|
def run(connection: Connection) -> List[User]:
|
||||||
return [
|
return [
|
||||||
User(cursor["username"], cursor["password"], UserAccess(cursor["access"]))
|
User(username=cursor["username"], password=cursor["password"], access=UserAccess(cursor["access"]))
|
||||||
for cursor in connection.execute(
|
for cursor in connection.execute(
|
||||||
"""
|
"""
|
||||||
select * from users
|
select * from users
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
from sqlite3 import Connection
|
from sqlite3 import Connection
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from ahriman.core.database.operations.operations import Operations
|
from ahriman.core.database.operations import Operations
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,23 +17,22 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sqlite3 import Connection, Cursor
|
from sqlite3 import Connection, Cursor
|
||||||
from typing import Any, Dict, Tuple, TypeVar, Callable
|
from typing import Any, Dict, Tuple, TypeVar, Callable
|
||||||
|
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class Operations:
|
class Operations(LazyLogging):
|
||||||
"""
|
"""
|
||||||
base operation class
|
base operation class
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
logger(logging.Logger): class logger
|
|
||||||
path(Path): path to the database file
|
path(Path): path to the database file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -45,7 +44,6 @@ class Operations:
|
|||||||
path(Path): path to the database file
|
path(Path): path to the database file
|
||||||
"""
|
"""
|
||||||
self.path = path
|
self.path = path
|
||||||
self.logger = logging.getLogger("database")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def factory(cursor: Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]:
|
def factory(cursor: Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]:
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
from sqlite3 import Connection
|
from sqlite3 import Connection
|
||||||
from typing import Dict, Generator, Iterable, List, Tuple
|
from typing import Dict, Generator, Iterable, List, Tuple
|
||||||
|
|
||||||
from ahriman.core.database.operations.operations import Operations
|
from ahriman.core.database.operations import Operations
|
||||||
from ahriman.models.build_status import BuildStatus
|
from ahriman.models.build_status import BuildStatus
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.package_description import PackageDescription
|
from ahriman.models.package_description import PackageDescription
|
||||||
@ -154,7 +154,11 @@ class PackageOperations(Operations):
|
|||||||
Dict[str, Package]: map of the package base to its descriptor (without packages themselves)
|
Dict[str, Package]: map of the package base to its descriptor (without packages themselves)
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
row["package_base"]: Package(row["package_base"], row["version"], RemoteSource.from_json(row), {})
|
row["package_base"]: Package(
|
||||||
|
base=row["package_base"],
|
||||||
|
version=row["version"],
|
||||||
|
remote=RemoteSource.from_json(row),
|
||||||
|
packages={})
|
||||||
for row in connection.execute("""select * from package_bases""")
|
for row in connection.execute("""select * from package_bases""")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
from sqlite3 import Connection
|
from sqlite3 import Connection
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from ahriman.core.database.operations.operations import Operations
|
from ahriman.core.database.operations import Operations
|
||||||
|
|
||||||
|
|
||||||
class PatchOperations(Operations):
|
class PatchOperations(Operations):
|
||||||
|
@ -27,10 +27,7 @@ from typing import Type
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database.migrations import Migrations
|
from ahriman.core.database.migrations import Migrations
|
||||||
from ahriman.core.database.operations.auth_operations import AuthOperations
|
from ahriman.core.database.operations import AuthOperations, BuildOperations, PackageOperations, PatchOperations
|
||||||
from ahriman.core.database.operations.build_operations import BuildOperations
|
|
||||||
from ahriman.core.database.operations.package_operations import PackageOperations
|
|
||||||
from ahriman.core.database.operations.patch_operations import PatchOperations
|
|
||||||
|
|
||||||
|
|
||||||
class SQLite(AuthOperations, BuildOperations, PackageOperations, PatchOperations):
|
class SQLite(AuthOperations, BuildOperations, PackageOperations, PatchOperations):
|
||||||
|
@ -27,3 +27,4 @@ from ahriman.core.formatters.package_printer import PackagePrinter
|
|||||||
from ahriman.core.formatters.status_printer import StatusPrinter
|
from ahriman.core.formatters.status_printer import StatusPrinter
|
||||||
from ahriman.core.formatters.update_printer import UpdatePrinter
|
from ahriman.core.formatters.update_printer import UpdatePrinter
|
||||||
from ahriman.core.formatters.user_printer import UserPrinter
|
from ahriman.core.formatters.user_printer import UserPrinter
|
||||||
|
from ahriman.core.formatters.version_printer import VersionPrinter
|
||||||
|
@ -44,7 +44,7 @@ class Printer:
|
|||||||
continue
|
continue
|
||||||
log_fn(f"\t{prop.name}{separator}{prop.value}")
|
log_fn(f"\t{prop.name}{separator}{prop.value}")
|
||||||
|
|
||||||
def properties(self) -> List[Property]: # pylint: disable=no-self-use
|
def properties(self) -> List[Property]:
|
||||||
"""
|
"""
|
||||||
convert content into printable data
|
convert content into printable data
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ from ahriman.core.formatters import Printer
|
|||||||
class StringPrinter(Printer):
|
class StringPrinter(Printer):
|
||||||
"""
|
"""
|
||||||
print content of the random string
|
print content of the random string
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
content(str): any content string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, content: str) -> None:
|
def __init__(self, content: str) -> None:
|
||||||
|
55
src/ahriman/core/formatters/version_printer.py
Normal file
55
src/ahriman/core/formatters/version_printer.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#
|
||||||
|
# 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 Dict, List
|
||||||
|
|
||||||
|
from ahriman.core.formatters import StringPrinter
|
||||||
|
from ahriman.models.property import Property
|
||||||
|
|
||||||
|
|
||||||
|
class VersionPrinter(StringPrinter):
|
||||||
|
"""
|
||||||
|
print content of the python package versions
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
packages(Dict[str, str]): map of package name to its version
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, title: str, packages: Dict[str, str]) -> None:
|
||||||
|
"""
|
||||||
|
default constructor
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title(str): title of the message
|
||||||
|
packages(Dict[str, str]): map of package name to its version
|
||||||
|
"""
|
||||||
|
StringPrinter.__init__(self, title)
|
||||||
|
self.packages = packages
|
||||||
|
|
||||||
|
def properties(self) -> List[Property]:
|
||||||
|
"""
|
||||||
|
convert content into printable data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Property]: list of content properties
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
Property(package, version, is_required=True)
|
||||||
|
for package, version in sorted(self.packages.items())
|
||||||
|
]
|
64
src/ahriman/core/lazy_logging.py
Normal file
64
src/ahriman/core/lazy_logging.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#
|
||||||
|
# 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 typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
class LazyLogging:
|
||||||
|
"""
|
||||||
|
wrapper for the logger library inspired by scala lazy logging module
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
logger(logging.Logger): class logger instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger: logging.Logger
|
||||||
|
|
||||||
|
def __getattr__(self, item: str) -> Any:
|
||||||
|
"""
|
||||||
|
logger extractor
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item(str) property name:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: attribute by its name
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: in case if no such attribute found
|
||||||
|
"""
|
||||||
|
if item == "logger":
|
||||||
|
logger = logging.getLogger(self.logger_name)
|
||||||
|
setattr(self, item, logger)
|
||||||
|
return logger
|
||||||
|
raise AttributeError(f"'{self.__class__.__qualname__}' object has no attribute '{item}'")
|
||||||
|
|
||||||
|
@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}{self.__class__.__qualname__}"
|
@ -18,11 +18,4 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from ahriman.core.report.report import Report
|
from ahriman.core.report.report import Report
|
||||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
|
||||||
|
|
||||||
from ahriman.core.report.console import Console
|
|
||||||
from ahriman.core.report.email import Email
|
|
||||||
from ahriman.core.report.html import HTML
|
|
||||||
from ahriman.core.report.telegram import Telegram
|
|
||||||
|
|
||||||
from ahriman.core.report.report_trigger import ReportTrigger
|
from ahriman.core.report.report_trigger import ReportTrigger
|
||||||
|
@ -25,7 +25,8 @@ from email.mime.text import MIMEText
|
|||||||
from typing import Dict, Iterable
|
from typing import Dict, Iterable
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.report import JinjaTemplate, Report
|
from ahriman.core.report import Report
|
||||||
|
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||||
from ahriman.core.util import pretty_datetime
|
from ahriman.core.util import pretty_datetime
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
@ -85,7 +86,7 @@ class Email(Report, JinjaTemplate):
|
|||||||
message = MIMEMultipart()
|
message = MIMEMultipart()
|
||||||
message["From"] = self.sender
|
message["From"] = self.sender
|
||||||
message["To"] = ", ".join(self.receivers)
|
message["To"] = ", ".join(self.receivers)
|
||||||
message["Subject"] = f"{self.name} build report at {pretty_datetime(datetime.datetime.utcnow().timestamp())}"
|
message["Subject"] = f"{self.name} build report at {pretty_datetime(datetime.datetime.utcnow())}"
|
||||||
|
|
||||||
message.attach(MIMEText(text, "html"))
|
message.attach(MIMEText(text, "html"))
|
||||||
for filename, content in attachment.items():
|
for filename, content in attachment.items():
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.report import JinjaTemplate, Report
|
from ahriman.core.report import Report
|
||||||
|
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
@ -19,25 +19,23 @@
|
|||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import Iterable, Type
|
from typing import Iterable, Type
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import ReportFailed
|
from ahriman.core.exceptions import ReportFailed
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.report_settings import ReportSettings
|
from ahriman.models.report_settings import ReportSettings
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
class Report:
|
class Report(LazyLogging):
|
||||||
"""
|
"""
|
||||||
base report generator
|
base report generator
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
logger(logging.Logger): class logger
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
``Report`` classes provide several method in order to operate with the report generation and additional class
|
``Report`` classes provide several method in order to operate with the report generation and additional class
|
||||||
@ -67,7 +65,6 @@ class Report:
|
|||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("root")
|
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
|
|
||||||
@ -87,16 +84,16 @@ class Report:
|
|||||||
section, provider_name = configuration.gettype(target, architecture)
|
section, provider_name = configuration.gettype(target, architecture)
|
||||||
provider = ReportSettings.from_option(provider_name)
|
provider = ReportSettings.from_option(provider_name)
|
||||||
if provider == ReportSettings.HTML:
|
if provider == ReportSettings.HTML:
|
||||||
from ahriman.core.report import HTML
|
from ahriman.core.report.html import HTML
|
||||||
return HTML(architecture, configuration, section)
|
return HTML(architecture, configuration, section)
|
||||||
if provider == ReportSettings.Email:
|
if provider == ReportSettings.Email:
|
||||||
from ahriman.core.report import Email
|
from ahriman.core.report.email import Email
|
||||||
return Email(architecture, configuration, section)
|
return Email(architecture, configuration, section)
|
||||||
if provider == ReportSettings.Console:
|
if provider == ReportSettings.Console:
|
||||||
from ahriman.core.report import Console
|
from ahriman.core.report.console import Console
|
||||||
return Console(architecture, configuration, section)
|
return Console(architecture, configuration, section)
|
||||||
if provider == ReportSettings.Telegram:
|
if provider == ReportSettings.Telegram:
|
||||||
from ahriman.core.report import Telegram
|
from ahriman.core.report.telegram import Telegram
|
||||||
return Telegram(architecture, configuration, section)
|
return Telegram(architecture, configuration, section)
|
||||||
return cls(architecture, configuration) # should never happen
|
return cls(architecture, configuration) # should never happen
|
||||||
|
|
||||||
|
@ -23,7 +23,8 @@ import requests
|
|||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.report import JinjaTemplate, Report
|
from ahriman.core.report import Report
|
||||||
|
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||||
from ahriman.core.util import exception_response_text
|
from ahriman.core.util import exception_response_text
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
@ -40,6 +41,7 @@ class Telegram(Report, JinjaTemplate):
|
|||||||
chat_id(str): chat id to post message, either string with @ or integer
|
chat_id(str): chat id to post message, either string with @ or integer
|
||||||
template_path(Path): path to template for built packages
|
template_path(Path): path to template for built packages
|
||||||
template_type(str): template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown
|
template_type(str): template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown
|
||||||
|
timeout(int): HTTP request timeout in seconds
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TELEGRAM_API_URL = "https://api.telegram.org"
|
TELEGRAM_API_URL = "https://api.telegram.org"
|
||||||
@ -61,6 +63,7 @@ class Telegram(Report, JinjaTemplate):
|
|||||||
self.chat_id = configuration.get(section, "chat_id")
|
self.chat_id = configuration.get(section, "chat_id")
|
||||||
self.template_path = configuration.getpath(section, "template_path")
|
self.template_path = configuration.getpath(section, "template_path")
|
||||||
self.template_type = configuration.get(section, "template_type", fallback="HTML")
|
self.template_type = configuration.get(section, "template_type", fallback="HTML")
|
||||||
|
self.timeout = configuration.getint(section, "timeout", fallback=30)
|
||||||
|
|
||||||
def _send(self, text: str) -> None:
|
def _send(self, text: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -72,7 +75,8 @@ class Telegram(Report, JinjaTemplate):
|
|||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage",
|
f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage",
|
||||||
data={"chat_id": self.chat_id, "text": text, "parse_mode": self.template_type})
|
data={"chat_id": self.chat_id, "text": text, "parse_mode": self.template_type},
|
||||||
|
timeout=self.timeout)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
self.logger.exception("could not perform request: %s", exception_response_text(e))
|
self.logger.exception("could not perform request: %s", exception_response_text(e))
|
||||||
@ -94,8 +98,11 @@ class Telegram(Report, JinjaTemplate):
|
|||||||
text = self.make_html(result, self.template_path)
|
text = self.make_html(result, self.template_path)
|
||||||
# telegram content is limited by 4096 symbols, so we are going to split the message by new lines
|
# telegram content is limited by 4096 symbols, so we are going to split the message by new lines
|
||||||
# to fit into this restriction
|
# to fit into this restriction
|
||||||
if len(text) > self.TELEGRAM_MAX_CONTENT_LENGTH:
|
while len(text) > self.TELEGRAM_MAX_CONTENT_LENGTH:
|
||||||
position = text.rfind("\n", 0, self.TELEGRAM_MAX_CONTENT_LENGTH)
|
position = text.rfind("\n", 0, self.TELEGRAM_MAX_CONTENT_LENGTH)
|
||||||
|
if position == -1:
|
||||||
|
# normally should not happen, but we allow templates editing
|
||||||
|
raise ValueError("substring not found")
|
||||||
portion, text = text[:position], text[position + 1:] # +1 to exclude newline we split
|
portion, text = text[:position], text[position + 1:] # +1 to exclude newline we split
|
||||||
self._send(portion)
|
self._send(portion)
|
||||||
# send remaining (or full in case if size is less than max length) text
|
# send remaining (or full in case if size is less than max length) text
|
||||||
|
@ -20,12 +20,14 @@
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Iterable, List, Optional, Set
|
from typing import Iterable, List, Optional, Set
|
||||||
|
|
||||||
from ahriman.core.build_tools.task import Task
|
from ahriman.core.build_tools.task import Task
|
||||||
from ahriman.core.repository.cleaner import Cleaner
|
from ahriman.core.repository.cleaner import Cleaner
|
||||||
from ahriman.core.util import tmpdir
|
from ahriman.core.util import safe_filename
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.package_description import PackageDescription
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
@ -82,7 +84,8 @@ class Executor(Cleaner):
|
|||||||
|
|
||||||
result = Result()
|
result = Result()
|
||||||
for single in updates:
|
for single in updates:
|
||||||
with tmpdir() as build_dir:
|
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, \
|
||||||
|
(build_dir := Path(dir_name)): # pylint: disable=confusing-with-statement
|
||||||
try:
|
try:
|
||||||
build_single(single, build_dir)
|
build_single(single, build_dir)
|
||||||
result.add_success(single)
|
result.add_success(single)
|
||||||
@ -122,16 +125,16 @@ class Executor(Cleaner):
|
|||||||
for local in self.packages():
|
for local in self.packages():
|
||||||
if local.base in packages or all(package in requested for package in local.packages):
|
if local.base in packages or all(package in requested for package in local.packages):
|
||||||
to_remove = {
|
to_remove = {
|
||||||
package: Path(properties.filename)
|
package: properties.filepath
|
||||||
for package, properties in local.packages.items()
|
for package, properties in local.packages.items()
|
||||||
if properties.filename is not None
|
if properties.filepath is not None
|
||||||
}
|
}
|
||||||
remove_base(local.base)
|
remove_base(local.base)
|
||||||
elif requested.intersection(local.packages.keys()):
|
elif requested.intersection(local.packages.keys()):
|
||||||
to_remove = {
|
to_remove = {
|
||||||
package: Path(properties.filename)
|
package: properties.filepath
|
||||||
for package, properties in local.packages.items()
|
for package, properties in local.packages.items()
|
||||||
if package in requested and properties.filename is not None
|
if package in requested and properties.filepath is not None
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
to_remove = {}
|
to_remove = {}
|
||||||
@ -160,17 +163,25 @@ class Executor(Cleaner):
|
|||||||
Returns:
|
Returns:
|
||||||
Result: path to repository database
|
Result: path to repository database
|
||||||
"""
|
"""
|
||||||
|
def rename(archive: PackageDescription, base: str) -> None:
|
||||||
|
if archive.filename is None:
|
||||||
|
self.logger.warning("received empty package name for base %s", base)
|
||||||
|
return # suppress type checking, it never can be none actually
|
||||||
|
if (safe := safe_filename(archive.filename)) != archive.filename:
|
||||||
|
shutil.move(self.paths.packages / archive.filename, self.paths.packages / safe)
|
||||||
|
archive.filename = safe
|
||||||
|
|
||||||
def update_single(name: Optional[str], base: str) -> None:
|
def update_single(name: Optional[str], base: str) -> None:
|
||||||
if name is None:
|
if name is None:
|
||||||
self.logger.warning("received empty package name for base %s", base)
|
self.logger.warning("received empty package name for base %s", base)
|
||||||
return # suppress type checking, it never can be none actually
|
return # suppress type checking, it never can be none actually
|
||||||
# in theory it might be NOT packages directory, but we suppose it is
|
# in theory, it might be NOT packages directory, but we suppose it is
|
||||||
full_path = self.paths.packages / name
|
full_path = self.paths.packages / name
|
||||||
files = self.sign.process_sign_package(full_path, base)
|
files = self.sign.process_sign_package(full_path, base)
|
||||||
for src in files:
|
for src in files:
|
||||||
dst = self.paths.repository / src.name
|
dst = self.paths.repository / safe_filename(src.name)
|
||||||
shutil.move(src, dst)
|
shutil.move(src, dst)
|
||||||
package_path = self.paths.repository / name
|
package_path = self.paths.repository / safe_filename(name)
|
||||||
self.repo.add(package_path)
|
self.repo.add(package_path)
|
||||||
|
|
||||||
current_packages = self.packages()
|
current_packages = self.packages()
|
||||||
@ -181,6 +192,7 @@ class Executor(Cleaner):
|
|||||||
for local in updates:
|
for local in updates:
|
||||||
try:
|
try:
|
||||||
for description in local.packages.values():
|
for description in local.packages.values():
|
||||||
|
rename(description, local.base)
|
||||||
update_single(description.filename, local.base)
|
update_single(description.filename, local.base)
|
||||||
self.reporter.set_success(local)
|
self.reporter.set_success(local)
|
||||||
result.add_success(local)
|
result.add_success(local)
|
||||||
|
@ -99,7 +99,7 @@ class Repository(Executor, UpdateHandler):
|
|||||||
"""
|
"""
|
||||||
return list(filter(package_like, self.paths.packages.iterdir()))
|
return list(filter(package_like, self.paths.packages.iterdir()))
|
||||||
|
|
||||||
def packages_depends_on(self, depends_on: Optional[Iterable[str]]) -> List[Package]:
|
def packages_depend_on(self, depends_on: Optional[Iterable[str]]) -> List[Package]:
|
||||||
"""
|
"""
|
||||||
extract list of packages which depends on specified package
|
extract list of packages which depends on specified package
|
||||||
|
|
||||||
|
@ -17,20 +17,19 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
|
|
||||||
from ahriman.core.alpm.pacman import Pacman
|
from ahriman.core.alpm.pacman import Pacman
|
||||||
from ahriman.core.alpm.repo import Repo
|
from ahriman.core.alpm.repo import Repo
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.core.exceptions import UnsafeRun
|
from ahriman.core.exceptions import UnsafeRun
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.sign.gpg import GPG
|
from ahriman.core.sign.gpg import GPG
|
||||||
from ahriman.core.status.client import Client
|
from ahriman.core.status.client import Client
|
||||||
from ahriman.core.triggers import TriggerLoader
|
from ahriman.core.triggers import TriggerLoader
|
||||||
from ahriman.core.util import check_user
|
from ahriman.core.util import check_user
|
||||||
|
|
||||||
|
|
||||||
class RepositoryProperties:
|
class RepositoryProperties(LazyLogging):
|
||||||
"""
|
"""
|
||||||
repository internal objects holder
|
repository internal objects holder
|
||||||
|
|
||||||
@ -39,7 +38,6 @@ class RepositoryProperties:
|
|||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
database(SQLite): database instance
|
database(SQLite): database instance
|
||||||
ignore_list(List[str]): package bases which will be ignored during auto updates
|
ignore_list(List[str]): package bases which will be ignored during auto updates
|
||||||
logger(logging.Logger): class logger
|
|
||||||
name(str): repository name
|
name(str): repository name
|
||||||
pacman(Pacman): alpm wrapper instance
|
pacman(Pacman): alpm wrapper instance
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
@ -61,7 +59,6 @@ class RepositoryProperties:
|
|||||||
no_report(bool): force disable reporting
|
no_report(bool): force disable reporting
|
||||||
unsafe(bool): if set no user check will be performed before path creation
|
unsafe(bool): if set no user check will be performed before path creation
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("root")
|
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.database = database
|
self.database = database
|
||||||
|
@ -72,8 +72,6 @@ class UpdateHandler(Cleaner):
|
|||||||
if local.is_outdated(remote, self.paths):
|
if local.is_outdated(remote, self.paths):
|
||||||
self.reporter.set_pending(local.base)
|
self.reporter.set_pending(local.base)
|
||||||
result.append(remote)
|
result.append(remote)
|
||||||
else:
|
|
||||||
self.reporter.set_success(local)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.reporter.set_failed(local.base)
|
self.reporter.set_failed(local.base)
|
||||||
self.logger.exception("could not load remote package %s", local.base)
|
self.logger.exception("could not load remote package %s", local.base)
|
||||||
@ -103,8 +101,6 @@ class UpdateHandler(Cleaner):
|
|||||||
elif local.is_outdated(remote, self.paths):
|
elif local.is_outdated(remote, self.paths):
|
||||||
self.reporter.set_pending(local.base)
|
self.reporter.set_pending(local.base)
|
||||||
result.append(remote)
|
result.append(remote)
|
||||||
else:
|
|
||||||
self.reporter.set_success(local)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.exception("could not process package at %s", dirname)
|
self.logger.exception("could not process package at %s", dirname)
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -25,23 +24,25 @@ from typing import List, Optional, Set, Tuple
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import BuildFailed
|
from ahriman.core.exceptions import BuildFailed
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.util import check_output, exception_response_text
|
from ahriman.core.util import check_output, exception_response_text
|
||||||
from ahriman.models.sign_settings import SignSettings
|
from ahriman.models.sign_settings import SignSettings
|
||||||
|
|
||||||
|
|
||||||
class GPG:
|
class GPG(LazyLogging):
|
||||||
"""
|
"""
|
||||||
gnupg wrapper
|
gnupg wrapper
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
default_key(Optional[str]): default PGP key ID to use
|
default_key(Optional[str]): default PGP key ID to use
|
||||||
logger(logging.Logger): class logger
|
|
||||||
targets(Set[SignSettings]): list of targets to sign (repository, package etc)
|
targets(Set[SignSettings]): list of targets to sign (repository, package etc)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_check_output = check_output
|
_check_output = check_output
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
|
||||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
@ -51,7 +52,6 @@ class GPG:
|
|||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("build_details")
|
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.targets, self.default_key = self.sign_options(configuration)
|
self.targets, self.default_key = self.sign_options(configuration)
|
||||||
@ -122,7 +122,7 @@ class GPG:
|
|||||||
"op": "get",
|
"op": "get",
|
||||||
"options": "mr",
|
"options": "mr",
|
||||||
"search": key
|
"search": key
|
||||||
})
|
}, timeout=self.DEFAULT_TIMEOUT)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
self.logger.exception("could not download key %s from %s: %s", key, server, exception_response_text(e))
|
self.logger.exception("could not download key %s from %s: %s", key, server, exception_response_text(e))
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from multiprocessing import Process, Queue
|
from multiprocessing import Process, Queue
|
||||||
@ -28,10 +27,11 @@ from threading import Lock, Thread
|
|||||||
from typing import Callable, Dict, Iterable, Tuple
|
from typing import Callable, Dict, Iterable, Tuple
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.models.package_source import PackageSource
|
from ahriman.models.package_source import PackageSource
|
||||||
|
|
||||||
|
|
||||||
class Spawn(Thread):
|
class Spawn(Thread, LazyLogging):
|
||||||
"""
|
"""
|
||||||
helper to spawn external ahriman process
|
helper to spawn external ahriman process
|
||||||
MUST NOT be used directly, the only one usage allowed is to spawn process from web services
|
MUST NOT be used directly, the only one usage allowed is to spawn process from web services
|
||||||
@ -40,7 +40,6 @@ class Spawn(Thread):
|
|||||||
active(Dict[str, Process]): map of active child processes required to avoid zombies
|
active(Dict[str, Process]): map of active child processes required to avoid zombies
|
||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
logger(logging.Logger): spawner logger
|
|
||||||
queue(Queue[Tuple[str, bool]]): multiprocessing queue to read updates from processes
|
queue(Queue[Tuple[str, bool]]): multiprocessing queue to read updates from processes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -57,7 +56,6 @@ class Spawn(Thread):
|
|||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.args_parser = args_parser
|
self.args_parser = args_parser
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.logger = logging.getLogger("http")
|
|
||||||
|
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
self.active: Dict[str, Process] = {}
|
self.active: Dict[str, Process] = {}
|
||||||
|
@ -60,7 +60,7 @@ class Client:
|
|||||||
status(BuildStatusEnum): current package build status
|
status(BuildStatusEnum): current package build status
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]: # pylint: disable=no-self-use
|
def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]:
|
||||||
"""
|
"""
|
||||||
get package status
|
get package status
|
||||||
|
|
||||||
@ -73,23 +73,14 @@ class Client:
|
|||||||
del base
|
del base
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_internal(self) -> InternalStatus: # pylint: disable=no-self-use
|
def get_internal(self) -> InternalStatus:
|
||||||
"""
|
"""
|
||||||
get internal service status
|
get internal service status
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
InternalStatus: current internal (web) service status
|
InternalStatus: current internal (web) service status
|
||||||
"""
|
"""
|
||||||
return InternalStatus()
|
return InternalStatus(status=BuildStatus())
|
||||||
|
|
||||||
def get_self(self) -> BuildStatus: # pylint: disable=no-self-use
|
|
||||||
"""
|
|
||||||
get ahriman status itself
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
BuildStatus: current ahriman status
|
|
||||||
"""
|
|
||||||
return BuildStatus()
|
|
||||||
|
|
||||||
def remove(self, base: str) -> None:
|
def remove(self, base: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -17,19 +17,18 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.core.exceptions import UnknownPackage
|
from ahriman.core.exceptions import UnknownPackage
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.repository import Repository
|
from ahriman.core.repository import Repository
|
||||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
class Watcher:
|
class Watcher(LazyLogging):
|
||||||
"""
|
"""
|
||||||
package status watcher
|
package status watcher
|
||||||
|
|
||||||
@ -38,7 +37,6 @@ class Watcher:
|
|||||||
database(SQLite): database instance
|
database(SQLite): database instance
|
||||||
known(Dict[str, Tuple[Package, BuildStatus]]): list of known packages. For the most cases ``packages`` should
|
known(Dict[str, Tuple[Package, BuildStatus]]): list of known packages. For the most cases ``packages`` should
|
||||||
be used instead
|
be used instead
|
||||||
logger(logging.Logger): class logger
|
|
||||||
repository(Repository): repository object
|
repository(Repository): repository object
|
||||||
status(BuildStatus): daemon status
|
status(BuildStatus): daemon status
|
||||||
"""
|
"""
|
||||||
@ -52,8 +50,6 @@ class Watcher:
|
|||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
database(SQLite): database instance
|
database(SQLite): database instance
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("http")
|
|
||||||
|
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.database = database
|
self.database = database
|
||||||
self.repository = Repository(architecture, configuration, database, no_report=True, unsafe=False)
|
self.repository = Repository(architecture, configuration, database, no_report=True, unsafe=False)
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.status.client import Client
|
from ahriman.core.status.client import Client
|
||||||
from ahriman.core.util import exception_response_text
|
from ahriman.core.util import exception_response_text
|
||||||
from ahriman.models.build_status import BuildStatusEnum, BuildStatus
|
from ahriman.models.build_status import BuildStatusEnum, BuildStatus
|
||||||
@ -31,13 +31,12 @@ from ahriman.models.package import Package
|
|||||||
from ahriman.models.user import User
|
from ahriman.models.user import User
|
||||||
|
|
||||||
|
|
||||||
class WebClient(Client):
|
class WebClient(Client, LazyLogging):
|
||||||
"""
|
"""
|
||||||
build status reporter web client
|
build status reporter web client
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
address(str): address of the web service
|
address(str): address of the web service
|
||||||
logger(logging.Logger): class logger
|
|
||||||
user(Optional[User]): web service user descriptor
|
user(Optional[User]): web service user descriptor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -48,7 +47,6 @@ class WebClient(Client):
|
|||||||
Args:
|
Args:
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("http")
|
|
||||||
self.address = self.parse_address(configuration)
|
self.address = self.parse_address(configuration)
|
||||||
self.user = User.from_option(
|
self.user = User.from_option(
|
||||||
configuration.get("web", "username", fallback=None),
|
configuration.get("web", "username", fallback=None),
|
||||||
@ -57,16 +55,6 @@ class WebClient(Client):
|
|||||||
self.__session = requests.session()
|
self.__session = requests.session()
|
||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
@property
|
|
||||||
def _ahriman_url(self) -> str:
|
|
||||||
"""
|
|
||||||
get url for the service status api
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: full url for web service for ahriman service itself
|
|
||||||
"""
|
|
||||||
return f"{self.address}/status-api/v1/ahriman"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _login_url(self) -> str:
|
def _login_url(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -75,7 +63,7 @@ class WebClient(Client):
|
|||||||
Returns:
|
Returns:
|
||||||
str: full url for web service to login
|
str: full url for web service to login
|
||||||
"""
|
"""
|
||||||
return f"{self.address}/user-api/v1/login"
|
return f"{self.address}/api/v1/login"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _status_url(self) -> str:
|
def _status_url(self) -> str:
|
||||||
@ -85,7 +73,7 @@ class WebClient(Client):
|
|||||||
Returns:
|
Returns:
|
||||||
str: full url for web service for status
|
str: full url for web service for status
|
||||||
"""
|
"""
|
||||||
return f"{self.address}/status-api/v1/status"
|
return f"{self.address}/api/v1/status"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_address(configuration: Configuration) -> str:
|
def parse_address(configuration: Configuration) -> str:
|
||||||
@ -136,7 +124,7 @@ class WebClient(Client):
|
|||||||
Returns:
|
Returns:
|
||||||
str: full url of web service for specific package base
|
str: full url of web service for specific package base
|
||||||
"""
|
"""
|
||||||
return f"{self.address}/status-api/v1/packages/{base}"
|
return f"{self.address}/api/v1/packages/{base}"
|
||||||
|
|
||||||
def add(self, package: Package, status: BuildStatusEnum) -> None:
|
def add(self, package: Package, status: BuildStatusEnum) -> None:
|
||||||
"""
|
"""
|
||||||
@ -201,26 +189,7 @@ class WebClient(Client):
|
|||||||
self.logger.exception("could not get web service status: %s", exception_response_text(e))
|
self.logger.exception("could not get web service status: %s", exception_response_text(e))
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.exception("could not get web service status")
|
self.logger.exception("could not get web service status")
|
||||||
return InternalStatus()
|
return InternalStatus(status=BuildStatus())
|
||||||
|
|
||||||
def get_self(self) -> BuildStatus:
|
|
||||||
"""
|
|
||||||
get ahriman status itself
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
BuildStatus: current ahriman status
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
response = self.__session.get(self._ahriman_url)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
status_json = response.json()
|
|
||||||
return BuildStatus.from_json(status_json)
|
|
||||||
except requests.HTTPError as e:
|
|
||||||
self.logger.exception("could not get service status: %s", exception_response_text(e))
|
|
||||||
except Exception:
|
|
||||||
self.logger.exception("could not get service status")
|
|
||||||
return BuildStatus()
|
|
||||||
|
|
||||||
def remove(self, base: str) -> None:
|
def remove(self, base: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -265,7 +234,7 @@ class WebClient(Client):
|
|||||||
payload = {"status": status.value}
|
payload = {"status": status.value}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.__session.post(self._ahriman_url, json=payload)
|
response = self.__session.post(self._status_url, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
self.logger.exception("could not update service status: %s", exception_response_text(e))
|
self.logger.exception("could not update service status: %s", exception_response_text(e))
|
||||||
|
@ -19,11 +19,12 @@
|
|||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Iterable, List, Set, Type
|
from typing import Iterable, List, Set, Type
|
||||||
|
|
||||||
from ahriman.core.build_tools.sources import Sources
|
from ahriman.core.build_tools.sources import Sources
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.core.util import tmpdir
|
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.repository_paths import RepositoryPaths
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
|
|
||||||
@ -71,7 +72,8 @@ class Leaf:
|
|||||||
Returns:
|
Returns:
|
||||||
Leaf: loaded class
|
Leaf: loaded class
|
||||||
"""
|
"""
|
||||||
with tmpdir() as clone_dir:
|
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, \
|
||||||
|
(clone_dir := Path(dir_name)): # pylint: disable=confusing-with-statement
|
||||||
Sources.load(clone_dir, package, database.patches_get(package.base), paths)
|
Sources.load(clone_dir, package, database.patches_get(package.base), paths)
|
||||||
dependencies = Package.dependencies(clone_dir)
|
dependencies = Package.dependencies(clone_dir)
|
||||||
return cls(package, dependencies)
|
return cls(package, dependencies)
|
||||||
|
@ -17,23 +17,21 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
class Trigger:
|
class Trigger(LazyLogging):
|
||||||
"""
|
"""
|
||||||
trigger base class
|
trigger base class
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
logger(logging.Logger): application logger
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
This class must be used in order to create own extension. Basically idea is the following::
|
This class must be used in order to create own extension. Basically idea is the following::
|
||||||
@ -61,7 +59,6 @@ class Trigger:
|
|||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("root")
|
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import os
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
@ -26,19 +26,19 @@ from typing import Iterable
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import InvalidExtension
|
from ahriman.core.exceptions import InvalidExtension
|
||||||
|
from ahriman.core.lazy_logging import LazyLogging
|
||||||
from ahriman.core.triggers import Trigger
|
from ahriman.core.triggers import Trigger
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
class TriggerLoader:
|
class TriggerLoader(LazyLogging):
|
||||||
"""
|
"""
|
||||||
trigger loader class
|
trigger loader class
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
logger(logging.Logger): application logger
|
|
||||||
triggers(List[Trigger]): list of loaded triggers according to the configuration
|
triggers(List[Trigger]): list of loaded triggers according to the configuration
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -65,12 +65,11 @@ class TriggerLoader:
|
|||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger("root")
|
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
|
|
||||||
self.triggers = [
|
self.triggers = [
|
||||||
self._load_trigger(trigger)
|
self.load_trigger(trigger)
|
||||||
for trigger in configuration.getlist("build", "triggers")
|
for trigger in configuration.getlist("build", "triggers")
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -103,6 +102,9 @@ class TriggerLoader:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ModuleType: module loaded from the imported module
|
ModuleType: module loaded from the imported module
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidExtension: in case if module cannot be loaded from specified package
|
||||||
"""
|
"""
|
||||||
self.logger.info("load module from package %s", package)
|
self.logger.info("load module from package %s", package)
|
||||||
try:
|
try:
|
||||||
@ -110,7 +112,7 @@ class TriggerLoader:
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
raise InvalidExtension(f"Module {package} not found")
|
raise InvalidExtension(f"Module {package} not found")
|
||||||
|
|
||||||
def _load_trigger(self, module_path: str) -> Trigger:
|
def load_trigger(self, module_path: str) -> Trigger:
|
||||||
"""
|
"""
|
||||||
load trigger by module path
|
load trigger by module path
|
||||||
|
|
||||||
@ -119,11 +121,16 @@ class TriggerLoader:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Trigger: loaded trigger based on settings
|
Trigger: loaded trigger based on settings
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidExtension: in case if module cannot be loaded from the specified module path or is not a trigger
|
||||||
"""
|
"""
|
||||||
*package_path_parts, class_name = module_path.split(".")
|
*package_path_parts, class_name = module_path.split(".")
|
||||||
package_or_path = ".".join(package_path_parts)
|
package_or_path = ".".join(package_path_parts)
|
||||||
|
|
||||||
if Path(package_or_path).is_file():
|
# it works for both missing permission and file does not exist
|
||||||
|
path_like = Path(package_or_path)
|
||||||
|
if os.access(path_like, os.R_OK) and path_like.is_file():
|
||||||
module = self._load_module_from_file(package_or_path, class_name)
|
module = self._load_module_from_file(package_or_path, class_name)
|
||||||
else:
|
else:
|
||||||
module = self._load_module_from_package(package_or_path)
|
module = self._load_module_from_package(package_or_path)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user