mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-11-15 13:03:42 +00:00
Compare commits
11 Commits
2.12.0
...
9c584689f6
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c584689f6 | |||
| 22de00f053 | |||
| f22c10c70d | |||
| e514740032 | |||
| 1b5bfe5088 | |||
| 0eebcc5740 | |||
| 7a24307224 | |||
| b7dc4c7ef4 | |||
| b6a9284b92 | |||
| 812bf07803 | |||
| 32b00de42c |
4
.github/workflows/setup.sh
vendored
4
.github/workflows/setup.sh
vendored
@ -22,8 +22,6 @@ if [[ -z $MINIMAL_INSTALL ]]; then
|
||||
# additional features
|
||||
pacman --noconfirm -Sy gnupg python-boto3 rsync
|
||||
fi
|
||||
# FIXME since 1.0.4 devtools requires dbus to be run, which doesn't work now in container
|
||||
cp "docker/systemd-nspawn.sh" "/usr/local/bin/systemd-nspawn"
|
||||
|
||||
# create fresh tarball
|
||||
make VERSION=1.0.0 archlinux # well, it does not really matter which version we will put here
|
||||
@ -42,7 +40,7 @@ ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot <ahriman@exa
|
||||
# validate configuration
|
||||
ahriman service-config-validate --exit-code
|
||||
# enable services
|
||||
systemctl enable ahriman-web
|
||||
systemctl enable ahriman-web@x86_64-github
|
||||
systemctl enable ahriman@x86_64-github.timer
|
||||
if [[ -z $MINIMAL_INSTALL ]]; then
|
||||
# run web service (detached)
|
||||
|
||||
4
.github/workflows/setup.yml
vendored
4
.github/workflows/setup.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: archlinux:base
|
||||
image: archlinux:latest
|
||||
volumes:
|
||||
- ${{ github.workspace }}:/build
|
||||
options: --privileged -w /build
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: archlinux:base
|
||||
image: archlinux:latest
|
||||
volumes:
|
||||
- ${{ github.workspace }}:/build
|
||||
options: --privileged -w /build
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: archlinux:base
|
||||
image: archlinux:latest
|
||||
volumes:
|
||||
- ${{ github.workspace }}:/build
|
||||
options: -w /build
|
||||
|
||||
@ -67,7 +67,7 @@ ignored-modules=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
init-hook='import sys; sys.path.append("pylint_plugins")'
|
||||
#init-hook=
|
||||
|
||||
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
|
||||
# number of processors available to use, and will cap the count on Windows to
|
||||
@ -81,8 +81,7 @@ limit-inference-results=100
|
||||
|
||||
# List of plugins (as comma separated values of python module names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=pylint.extensions.docparams,
|
||||
definition_order,
|
||||
load-plugins=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
@ -228,7 +227,7 @@ name-group=
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
|
||||
@ -26,7 +26,7 @@ In order to resolve all difficult cases the `autopep8` is used. You can perform
|
||||
Again, the most checks can be performed by `make check` command, though some additional guidelines must be applied:
|
||||
|
||||
* Every class, every function (including private and protected), every attribute must be documented. The project follows [Google style documentation](https://google.github.io/styleguide/pyguide.html). The only exception is local functions.
|
||||
* Correct way to document function (if a section is empty, e.g. no notes or there are no args, it should be omitted) is the following:
|
||||
* Correct way to document function, if section is empty, e.g. no notes or there are no args, it should be omitted:
|
||||
|
||||
```python
|
||||
def foo(argument: str, *, flag: bool = False) -> int:
|
||||
@ -43,8 +43,7 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
flag(bool, optional): a flag (Default value = False)
|
||||
|
||||
Returns:
|
||||
int: result with
|
||||
long description
|
||||
int: result
|
||||
|
||||
Raises:
|
||||
RuntimeError: a local function error occurs
|
||||
@ -65,7 +64,7 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
```python
|
||||
class Clazz(BaseClazz):
|
||||
"""
|
||||
brand-new implementation of :class:`BaseClazz`
|
||||
brand-new implementation of ``BaseClazz``
|
||||
|
||||
Attributes:
|
||||
CLAZZ_ATTRIBUTE(int): (class attribute) a brand-new class attribute
|
||||
@ -93,7 +92,7 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
|
||||
* Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated.
|
||||
* For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typinng.Optional` (e.g. `str | None` instead of `Optional[str]`).
|
||||
* `classmethod` should (almost) always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead.
|
||||
* `classmethod` should always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead.
|
||||
* Recommended order of function definitions in class:
|
||||
|
||||
```python
|
||||
@ -122,12 +121,10 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
def __hash__(self) -> int: ... # basically any magic (or look-alike) method
|
||||
```
|
||||
|
||||
Methods inside one group should be ordered alphabetically, the only exceptions are `__init__` (`__post_init__` for dataclasses) and `__new__` methods which should be defined first. For test methods it is recommended to follow the order in which functions are defined.
|
||||
Methods inside one group should be ordered alphabetically, the only exception is `__init__` method (`__post__init__` for dataclasses) which should be defined first. For test methods it is recommended to follow the order in which functions are defined.
|
||||
|
||||
Though, we would like to highlight abstract methods (i.e. ones which raise `NotImplementedError`), we still keep in global order at the moment.
|
||||
|
||||
For the most cases there is custom `pylint` plugin, which performs checks automatically.
|
||||
|
||||
* Abstract methods must raise `NotImplementedError` instead of using `abc.abstractmethod`. The reason behind this restriction is the fact that we have class/static abstract methods for those we need to define their attribute first making the code harder to read.
|
||||
* For any path interactions `pathlib.Path` must be used.
|
||||
* Configuration interactions must go through `ahriman.core.configuration.Configuration` class instance.
|
||||
@ -170,7 +167,7 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
* No global variable is allowed outside of `ahriman` module. `ahriman.core.context` is also special case.
|
||||
* Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent.
|
||||
* If your class writes anything to log, the `ahriman.core.log.LazyLogging` trait must be used.
|
||||
* Web API methods must be documented by using `aiohttp_apispec` library. The schema testing mostly should be implemented in related view class tests. Recommended example for documentation (excluding comments):
|
||||
* Web API methods must be documented by using `aiohttp_apispec` library. Schema testing mostly should be implemented in related view class tests. Recommended example for documentation (excluding comments):
|
||||
|
||||
```python
|
||||
import aiohttp_apispec
|
||||
@ -194,7 +191,6 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
class Foo(BaseView):
|
||||
|
||||
POST_PERMISSION = ...
|
||||
ROUTES = ...
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Tag"],
|
||||
@ -206,7 +202,6 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, # exception raised by this method
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema}, # should be always presented
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema}, # should be always presented
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema}, # include if BaseView.service() method is called
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema}, # should be always presented
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
@ -220,7 +215,6 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
|
||||
* It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced.
|
||||
* On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release.
|
||||
* Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise.
|
||||
|
||||
### Other checks
|
||||
|
||||
|
||||
@ -10,8 +10,6 @@ ENV AHRIMAN_OUTPUT=""
|
||||
ENV AHRIMAN_PACKAGER="ahriman bot <ahriman@example.com>"
|
||||
ENV AHRIMAN_PACMAN_MIRROR=""
|
||||
ENV AHRIMAN_PORT=""
|
||||
ENV AHRIMAN_POSTSETUP_COMMAND=""
|
||||
ENV AHRIMAN_PRESETUP_COMMAND=""
|
||||
ENV AHRIMAN_REPOSITORY="aur-clone"
|
||||
ENV AHRIMAN_REPOSITORY_SERVER=""
|
||||
ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman"
|
||||
@ -37,9 +35,6 @@ RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-i
|
||||
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-jinja2 \
|
||||
python-aiohttp-debugtoolbar python-aiohttp-session python-aiohttp-security
|
||||
|
||||
## FIXME since 1.0.4 devtools requires dbus to be run, which doesn't work now in container
|
||||
COPY "docker/systemd-nspawn.sh" "/usr/local/bin/systemd-nspawn"
|
||||
|
||||
# install ahriman
|
||||
## copy tree
|
||||
COPY --chown=build . "/home/build/ahriman"
|
||||
|
||||
2
Makefile
2
Makefile
@ -3,7 +3,7 @@
|
||||
|
||||
PROJECT := ahriman
|
||||
|
||||
FILES := AUTHORS CONTRIBUTING.md COPYING README.md SECURITY.md package pyproject.toml src web.png
|
||||
FILES := AUTHORS CONTRIBUTING.md COPYING Makefile README.md SECURITY.md docs package pyproject.toml src tox.ini web.png
|
||||
TARGET_FILES := $(addprefix $(PROJECT)/, $(FILES))
|
||||
IGNORE_FILES := package/archlinux src/.mypy_cache
|
||||
|
||||
|
||||
18
README.md
18
README.md
@ -1,42 +1,42 @@
|
||||
# ArcH linux ReposItory MANager
|
||||
|
||||
[](https://github.com/arcan1s/ahriman/actions/workflows/tests.yml)
|
||||
[](https://github.com/arcan1s/ahriman/actions/workflows/setup.yml)
|
||||
[](https://github.com/arcan1s/ahriman/actions/workflows/run-tests.yml)
|
||||
[](https://github.com/arcan1s/ahriman/actions/workflows/run-setup.yml)
|
||||
[](https://hub.docker.com/r/arcan1s/ahriman)
|
||||
[](https://www.codefactor.io/repository/github/arcan1s/ahriman)
|
||||
[](https://ahriman.readthedocs.io)
|
||||
[](https://ahriman.readthedocs.io/?badge=latest)
|
||||
|
||||
Wrapper for managing custom repository inspired by [repo-scripts](https://github.com/arcan1s/repo-scripts).
|
||||
|
||||
## Features
|
||||
|
||||
* Install-configure-forget manager for the very own repository.
|
||||
* Multi architecture and repository support.
|
||||
* Multi-architecture support.
|
||||
* Dependency manager.
|
||||
* VCS packages support.
|
||||
* Official repository support.
|
||||
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
||||
* Various rebuild options with ability to automatically bump package version.
|
||||
* Sign support with gpg (repository, package), multiple packagers support.
|
||||
* Triggers for repository updates, e.g. synchronization to remote services (rsync, S3 and GitHub) and report generation (email, html, telegram).
|
||||
* 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:
|
||||
|
||||

|
||||
|
||||
## Installation and run
|
||||
|
||||
For installation details kindly refer to the [documentation](https://ahriman.readthedocs.io/en/stable/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/stable/command-line.html)).
|
||||
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
|
||||
|
||||
Every available option is described in the [documentation](https://ahriman.readthedocs.io/en/stable/configuration.html).
|
||||
Every available option is described in the [documentation](https://ahriman.readthedocs.io/en/latest/configuration.html).
|
||||
|
||||
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/stable/faq.html)
|
||||
## [FAQ](https://ahriman.readthedocs.io/en/latest/faq.html)
|
||||
|
||||
## Live demos
|
||||
|
||||
* [Build status page](https://ahriman-demo.arcanis.me). You can log in as `demo` user by using `demo` password. However, you will not be able to run tasks. [HTTP API documentation](https://ahriman-demo.arcanis.me/api-docs) is also available.
|
||||
* [Repository index](https://repo.arcanis.me/arcanisrepo/x86_64/).
|
||||
* [Repository index](http://repo.arcanis.me/x86_64/index.html).
|
||||
* [Telegram feed](https://t.me/arcanisrepo).
|
||||
|
||||
@ -49,10 +49,7 @@ fi
|
||||
if [ -n "$AHRIMAN_UNIX_SOCKET" ]; then
|
||||
AHRIMAN_SETUP_ARGS+=("--web-unix-socket" "$AHRIMAN_UNIX_SOCKET")
|
||||
fi
|
||||
|
||||
[ -n "$AHRIMAN_PRESETUP_COMMAND" ] && eval "$AHRIMAN_PRESETUP_COMMAND"
|
||||
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" service-setup "${AHRIMAN_SETUP_ARGS[@]}"
|
||||
[ -n "$AHRIMAN_POSTSETUP_COMMAND" ] && eval "$AHRIMAN_POSTSETUP_COMMAND"
|
||||
|
||||
# validate configuration if set
|
||||
[ -n "$AHRIMAN_VALIDATE_CONFIGURATION" ] && ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" service-config-validate --exit-code
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Since https://gitlab.archlinux.org/archlinux/devtools/-/commit/5f4fd52e3836ddddb25a0f9e15d0acfed06f693d
|
||||
# it is impossible to start devtools inside docker container, because it requires slice registering
|
||||
# which is impossible because there is no init in container
|
||||
|
||||
is_slice() {
|
||||
[[ $1 =~ ^--slice* ]]
|
||||
}
|
||||
|
||||
allowed=()
|
||||
for arg in "$@"; do
|
||||
is_slice "$arg" && allowed+=("--keep-unit") || allowed+=("$arg")
|
||||
done
|
||||
|
||||
exec /usr/bin/systemd-nspawn "${allowed[@]}"
|
||||
@ -9,9 +9,8 @@ Depending on the goal the package can be used in different ways. Nevertheless, i
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
repository_id = RepositoryId("x86_64", "aur-clone")
|
||||
architecture = "x86_64"
|
||||
configuration = Configuration.from_path(Path("/etc/ahriman.ini"), architecture)
|
||||
database = SQLite.load(configuration)
|
||||
|
||||
@ -27,10 +26,8 @@ Almost all actions are wrapped by ``ahriman.core.repository.Repository`` class
|
||||
.. code-block:: python
|
||||
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
|
||||
repository = Repository(repository_id, configuration, database,
|
||||
report=True, refresh_pacman_database=PacmanSynchronization.Disabled)
|
||||
repository = Repository(architecture, configuration, database, report=True, unsafe=False)
|
||||
|
||||
And the ``repository`` instance can be used to perform repository maintenance
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 839 KiB |
@ -100,14 +100,6 @@ ahriman.application.handlers.remove\_unknown module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.repositories module
|
||||
------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.application.handlers.repositories
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.restore module
|
||||
-------------------------------------------
|
||||
|
||||
@ -116,14 +108,6 @@ ahriman.application.handlers.restore module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.run module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: ahriman.application.handlers.run
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.search module
|
||||
------------------------------------------
|
||||
|
||||
|
||||
@ -60,14 +60,6 @@ ahriman.core.formatters.printer module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.repository\_printer module
|
||||
--------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.formatters.repository_printer
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.status\_printer module
|
||||
----------------------------------------------
|
||||
|
||||
|
||||
@ -140,14 +140,6 @@ ahriman.models.pkgbuild\_patch module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.process\_status module
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.process_status
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.property module
|
||||
------------------------------
|
||||
|
||||
|
||||
@ -100,14 +100,6 @@ ahriman.web.schemas.package\_names\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.package\_patch\_schema module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.package_patch_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.package\_properties\_schema module
|
||||
------------------------------------------------------
|
||||
|
||||
@ -140,22 +132,6 @@ ahriman.web.schemas.pagination\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.patch\_name\_schema module
|
||||
----------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.patch_name_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.patch\_schema module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.patch_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.pgp\_key\_id\_schema module
|
||||
-----------------------------------------------
|
||||
|
||||
@ -196,14 +172,6 @@ ahriman.web.schemas.remote\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.repository\_id\_schema module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.repository_id_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.search\_schema module
|
||||
-----------------------------------------
|
||||
|
||||
@ -228,14 +196,6 @@ ahriman.web.schemas.update\_flags\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.versioned\_log\_schema module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.versioned_log_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
||||
@ -30,14 +30,6 @@ ahriman.web.views.index module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.static module
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.static
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
||||
@ -28,30 +28,6 @@ ahriman.web.views.v1.status.packages module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.patch module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.patch
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.patches module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.patches
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.repositories module
|
||||
-----------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.repositories
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.status module
|
||||
-----------------------------------------
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ Full dependency diagram:
|
||||
``ahriman.application`` package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This package contains application (aka executable) related classes and everything for it. 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.
|
||||
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`` (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.
|
||||
|
||||
@ -44,7 +44,7 @@ This package contains everything required for the most of application actions an
|
||||
* ``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.support`` provides plugins for support packages (mirrorlist and keyring) generation.
|
||||
* ``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``, ``ahriman.core.upload`` and other built-in triggers use this package.
|
||||
* ``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, should not be called directly.
|
||||
|
||||
This package also provides some generic functions and classes which may be used by other packages:
|
||||
@ -56,7 +56,7 @@ This package also provides some generic functions and classes which may be used
|
||||
``ahriman.models`` package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It provides models for any other part of application. Unlike ``ahriman.core`` package classes from here provide only conversion methods (e.g. create class from another or convert to). It is mostly presented by case classes and enumerations.
|
||||
It provides models for any other part of application. Unlike ``ahriman.core`` package classes from here provide only conversion methods (e.g. create class from another or convert to). Mostly case classes and enumerations.
|
||||
|
||||
``ahriman.web`` package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -74,9 +74,9 @@ Web application. It is important that this package is isolated from any other to
|
||||
Application run
|
||||
---------------
|
||||
|
||||
#. Parse command line arguments, find subcommand and related handler which is set by the parser.
|
||||
#. Parse command line arguments, find command and related handler which is set by parser.
|
||||
#. Call ``Handler.execute`` method.
|
||||
#. Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or continue in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether the 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).
|
||||
#. 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.
|
||||
#. 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.
|
||||
@ -84,7 +84,7 @@ Application run
|
||||
|
||||
In the most cases handlers spawn god class ``ahriman.application.application.Application`` class and call required methods.
|
||||
|
||||
The application is designed to run from ``systemd`` services and provides parametrized by repository identifier timer and service file for that.
|
||||
Application is designed to run from ``systemd`` services and provides parametrized by architecture timer and service file for that.
|
||||
|
||||
Subcommand design
|
||||
^^^^^^^^^^^^^^^^^
|
||||
@ -100,12 +100,12 @@ All subcommands are divided into several groups depending on the role they are d
|
||||
* ``user`` subcommands (``user-add``) are intended for user management.
|
||||
* ``web`` subcommands are related to web service management.
|
||||
|
||||
For historical reasons and in order to keep backward compatibility some subcommands have aliases to their shorter forms or even other groups, but the application doesn't guarantee that they will remain unchanged.
|
||||
For historical reasons and in order to keep backward compatibility some subcommands have aliases to their shorter forms or even other groups, but the service doesn't guarantee that they will remain unchanged.
|
||||
|
||||
Filesystem tree
|
||||
---------------
|
||||
|
||||
The application supports two types of trees, one is for the legacy configuration (when there were no repository name explicit configuration available) and another one is the new-style tree. This document describes only new-style tree in order to avoid deprecated structures.
|
||||
The application supports two types of trees, one is for legacy configuration (when there were no repository name explicit configuration available) and another one is for new-style tree. This document describes only new-style tree in order to avoid deprecated structure.
|
||||
|
||||
Having default root as ``/var/lib/ahriman`` (differs from container though), the directory structure is the following:
|
||||
|
||||
@ -142,10 +142,12 @@ There are multiple subdirectories, some of them are commons for any repository,
|
||||
* ``cache`` is a directory with locally stored PKGBUILD's and VCS packages. It is common for all repositories and architectures.
|
||||
* ``chroot/{repository}`` is a chroot directory for ``devtools``. It is specific for each repository, but shared for different architectures inside (the ``devtools`` handles architectures automatically).
|
||||
* ``packages/{repository}/{architecture}`` is a directory with prebuilt packages. When package is built, first it will be uploaded to this directory and later will be handled by update process. It is architecture and repository specific.
|
||||
* ``pacman/{repository}/{architecture}`` is the repository and architecture specific caches for pacman's databases.
|
||||
* ``pacman/{repository}/{architecture}`` is repository and architecture specific caches for pacman's databases.
|
||||
* ``repository/{repository}/{architecture}`` is a repository packages directory.
|
||||
|
||||
Normally you should avoid direct interaction with the application tree. For tree migration process refer to the :doc:`migration notes <migration>`.
|
||||
Normally you should avoid direct interaction with the application tree.
|
||||
|
||||
For tree migration process refer to the :doc:`migration notes <migration>`.
|
||||
|
||||
Database
|
||||
--------
|
||||
@ -164,7 +166,7 @@ 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 automatically and applies them in alphabetical order.
|
||||
|
||||
These migrations can also contain data migrations. Though the recommended way is to migrate data directly from SQL requests, sometimes it is required to have external data (like packages list) in order to set correct data. To do so, special method ``migrate_data`` is used.
|
||||
These migrations can also contain data migrations. Though the recommended way is to migrate data directly from SQL requests, sometimes it is required to have external data (like packages list) in order to set correct data. To do so, special method `migrate_data` is used.
|
||||
|
||||
Type conversions
|
||||
^^^^^^^^^^^^^^^^
|
||||
@ -191,7 +193,7 @@ Idea is to copy package to the directory from which it will be handled at the ne
|
||||
* 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 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. Also official repositories calls are hidden behind explicit source definition.
|
||||
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.
|
||||
|
||||
Rebuild packages
|
||||
^^^^^^^^^^^^^^^^
|
||||
@ -242,12 +244,12 @@ Core functions reference
|
||||
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 repository identifier 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 in general 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).
|
||||
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.
|
||||
|
||||
@ -261,7 +263,7 @@ Context variables
|
||||
|
||||
Package provides implicit global variables which can be accessed from ``ahriman.core`` package as ``context`` variable, wrapped by ``contextvars.ContextVar`` class. The value of the variable is defaulting to private ``_Context`` class which is defined in the same module. The default values - such as ``database`` and ``sign`` - are being set on the service initialization.
|
||||
|
||||
The ``_Context`` class itself mimics default collection interface (as is ``Mapping``) and can be modified by ``_Context.set`` method. The stored variables can be achieved by ``_Context.get`` method, which is unlike default ``Mapping`` interface also performs type and presence checks.
|
||||
The ``_Context`` class itself mimics default collection interface (as is Mapping) and can be modified by ``_Context.set`` method. The stored variables can be achieved by ``_Context.get`` method, which is unlike default ``Mapping`` interface also performs type and presence checks.
|
||||
|
||||
In order to provide statically typed interface, the ``ahriman.models.context_key.ContextKey`` class is used for both ``_Content.get`` and ``_Content.set`` methods; the context instance itself, however, does not store information about types.
|
||||
|
||||
@ -297,7 +299,7 @@ OAuth's implementation also allows authenticating users via username + password
|
||||
|
||||
In addition, web service checks the source socket used. In case if it belongs to ``socket.AF_UNIX`` family, it will skip any further checks considering the request to be performed in safe environment (e.g. on the same physical machine). This feature, in particular is being used by the reporter instances in case if socket address is set in configuration.
|
||||
|
||||
In order to configure users there are special subcommands.
|
||||
In order to configure users there are special commands.
|
||||
|
||||
Triggers
|
||||
^^^^^^^^
|
||||
@ -308,7 +310,7 @@ The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.
|
||||
|
||||
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``list[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
|
||||
|
||||
In addition triggers can implement ``on_start`` and ``on_stop`` actions which will be called on the application start and right before the application exit respectively. The ``on_start`` action is usually being called from handlers directly in order to make sure that no trigger will be run when it is not required (e.g. on user management). As soon as ``on_start`` action is called, the additional flag will be set; ``ahriman.core.triggers.TriggerLoader`` class implements ``__del__`` method in which, if the flag is set, the ``on_stop`` actions will be called.
|
||||
In addition triggers can implement ``on_start`` and ``on_stop`` actions which will be called on the application start and right before the application exit. The ``on_start`` action is usually being called from handlers directly in order to make sure that no trigger will be run when it is not required (e.g. on user management). As soon as ``on_start`` action is called, the additional flag will be set; ``ahriman.core.triggers.TriggerLoader`` class implements ``__del__`` method in which, if the flag is set, the ``on_stop`` actions will be called.
|
||||
|
||||
For more details how to deal with the triggers, refer to :doc:`documentation <triggers>` and modules descriptions.
|
||||
|
||||
@ -368,11 +370,9 @@ REST API supports both form and JSON data, but the last one is recommended.
|
||||
Different APIs are separated into different packages:
|
||||
|
||||
* ``ahriman.web.views.api`` not a real API, but some views which provide OpenAPI support.
|
||||
* ``ahriman.web.views.*.service`` provides views for application controls.
|
||||
* ``ahriman.web.views.*.status`` package provides REST API for application reporting.
|
||||
* ``ahriman.web.views.*.user`` package provides login and logout methods which can be called without authorization.
|
||||
|
||||
The views are also divided by supporting API versions (e.g. ``v1``, ``v2``).
|
||||
* ``ahriman.web.views.service`` provides views for application controls.
|
||||
* ``ahriman.web.views.status`` package provides REST API for application reporting.
|
||||
* ``ahriman.web.views.user`` package provides login and logout methods which can be called without authorization.
|
||||
|
||||
Templating
|
||||
^^^^^^^^^^
|
||||
|
||||
@ -3,10 +3,10 @@ Configuration
|
||||
|
||||
Some groups can be specified for each architecture and/or repository 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. The order which will be used for option resolution is the following:
|
||||
|
||||
#. Repository and architecture specific, e.g. ``build:aur-clone:x86_64``.
|
||||
#. Repository specific, e.g. ``build:aur-clone``.
|
||||
#. Architecture specific, e.g. ``build:x86_64``.
|
||||
#. Default section, e.g. ``build``.
|
||||
1. Repository and architecture specific, e.g. ``build:aur-clone:x86_64``.
|
||||
2. Repository specific, e.g. ``build:aur-clone``.
|
||||
2. Architecture specific, e.g. ``build:x86_64``.
|
||||
2. Default section, e.g. ``build``.
|
||||
|
||||
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:
|
||||
|
||||
@ -41,7 +41,7 @@ Base configuration settings.
|
||||
|
||||
* ``apply_migrations`` - perform migrations on application start, boolean, optional, default ``yes``. Useful if you are using git version. Note, however, that this option must be changed only if you know what to do and going to handle migrations automatically.
|
||||
* ``database`` - path to SQLite database, string, required.
|
||||
* ``include`` - path to directory with configuration files overrides, string, optional.
|
||||
* ``include`` - path to directory with configuration files overrides, string, required.
|
||||
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
||||
* ``suppress_http_log_errors`` - suppress http log errors, boolean, optional, default ``no``. If set to ``yes``, any http log errors (e.g. if web server is not available, but http logging is enabled) will be suppressed.
|
||||
|
||||
@ -67,7 +67,6 @@ Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` libr
|
||||
* ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used.
|
||||
* ``cookie_secret_key`` - secret key which will be used for cookies encryption, string, optional. It must be 32 url-safe base64-encoded bytes and can be generated as following ``base64.urlsafe_b64encode(os.urandom(32)).decode("utf8")``. If not set, it will be generated automatically; note, however, that in this case, all sessions will be automatically expired during restart.
|
||||
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
|
||||
* ``oauth_icon`` - OAuth2 login button icon, string, optional, default is ``google``. Must be valid `Bootstrap icon <https://icons.getbootstrap.com/>`_ name.
|
||||
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
|
||||
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
|
||||
* ``salt`` - additional password hash salt, string, optional.
|
||||
@ -103,10 +102,10 @@ Settings for signing packages or repository. Group name can refer to architectur
|
||||
* ``target`` - configuration flag to enable signing, space separated list of strings, required. Allowed values are ``package`` (sign each package separately), ``repository`` (sign repository database file).
|
||||
* ``key`` - default PGP key, string, required. This key will also be used for database signing if enabled.
|
||||
|
||||
``web`` group
|
||||
-------------
|
||||
``web:*`` groups
|
||||
----------------
|
||||
|
||||
Web server settings. If any of ``host``/``port`` is not set, web integration will be disabled. This feature requires ``aiohttp`` libraries to be installed.
|
||||
Web server settings. If any of ``host``/``port`` is not set, web integration will be disabled. Group name can refer to architecture, e.g. ``web:x86_64`` can be used for x86_64 architecture specific settings. This feature requires ``aiohttp`` libraries to be installed.
|
||||
|
||||
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
|
||||
* ``debug`` - enable debug toolbar, boolean, optional, default ``no``.
|
||||
@ -119,7 +118,7 @@ Web server settings. If any of ``host``/``port`` is not set, web integration wil
|
||||
* ``password`` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||
* ``port`` - port to bind, int, optional.
|
||||
* ``static_path`` - path to directory with static files, string, required.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``templates`` - path to templates directory, string, required.
|
||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
||||
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
||||
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
|
||||
@ -232,6 +231,7 @@ Section name must be either ``console`` (plus optional architecture name, e.g. `
|
||||
Section name must be either ``email`` (plus optional architecture name, e.g. ``email:x86_64``) or random name with ``type`` set.
|
||||
|
||||
* ``type`` - type of the report, string, optional, must be set to ``email`` if exists.
|
||||
* ``full_template_path`` - path to Jinja2 template for full package description index, string, optional.
|
||||
* ``homepage`` - link to homepage, string, optional.
|
||||
* ``host`` - SMTP host for sending emails, string, required.
|
||||
* ``link_path`` - prefix for HTML links, string, required.
|
||||
@ -241,9 +241,7 @@ Section name must be either ``email`` (plus optional architecture name, e.g. ``e
|
||||
* ``receivers`` - SMTP receiver addresses, space separated list of strings, required.
|
||||
* ``sender`` - SMTP sender address, string, required.
|
||||
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
|
||||
* ``template`` - Jinja2 template name, string, required.
|
||||
* ``template_full`` - Jinja2 template name for full package description index, string, optional.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``template_path`` - path to Jinja2 template, string, required.
|
||||
* ``user`` - SMTP user to authenticate, string, optional.
|
||||
|
||||
``html`` type
|
||||
@ -255,8 +253,7 @@ Section name must be either ``html`` (plus optional architecture name, e.g. ``ht
|
||||
* ``homepage`` - link to homepage, string, optional.
|
||||
* ``link_path`` - prefix for HTML links, string, required.
|
||||
* ``path`` - path to html report file, string, required.
|
||||
* ``template`` - Jinja2 template name, string, required.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``template_path`` - path to Jinja2 template, string, required.
|
||||
|
||||
``remote-call`` type
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
@ -279,9 +276,8 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
|
||||
* ``chat_id`` - telegram chat id, either string with ``@`` or integer value, required.
|
||||
* ``homepage`` - link to homepage, string, optional.
|
||||
* ``link_path`` - prefix for HTML links, string, required.
|
||||
* ``template`` - Jinja2 template name, 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``.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
||||
|
||||
``upload`` group
|
||||
|
||||
48
docs/faq.rst
48
docs/faq.rst
@ -200,14 +200,6 @@ Alternatively you can create full-diff patches, which are calculated by using ``
|
||||
|
||||
The last command will calculate diff from current tree to the ``HEAD`` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
|
||||
|
||||
It is also possible to create simple patch during package addition, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add ahriman --variable PKGEXT=.pkg.tar.xz
|
||||
|
||||
The ``--variable`` argument accepts variables in shell like format: quotation and lists are supported as usual, but functions are not. This feature is useful in particular in order to override specific makepkg variables during build.
|
||||
|
||||
How to build package from official repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -410,8 +402,6 @@ The following environment variables are supported:
|
||||
* ``AHRIMAN_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot <ahriman@example.com>``.
|
||||
* ``AHRIMAN_PACMAN_MIRROR`` - override pacman mirror server if set.
|
||||
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
|
||||
* ``AHRIMAN_POSTSETUP_COMMAND`` - if set, the command which will be called (as root) after the setup command, but before any other actions.
|
||||
* ``AHRIMAN_PRESETUP_COMMAND`` - if set, the command which will be called (as root) right before the setup command.
|
||||
* ``AHRIMAN_REPOSITORY`` - repository name, default is ``aur-clone``.
|
||||
* ``AHRIMAN_REPOSITORY_SERVER`` - optional override for the repository url. Useful if you would like to download packages from remote instead of local filesystem.
|
||||
* ``AHRIMAN_REPOSITORY_ROOT`` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses ``ahriman`` directory inside ahriman's home, which can be passed as mount volume.
|
||||
@ -439,7 +429,7 @@ This command uses same rules as ``repo-update``, thus, e.g. requires ``--privile
|
||||
Web service setup
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
For that you would need to have web container instance running forever; it can be achieved by the following command:
|
||||
Well for that you would need to have web container instance running forever; it can be achieved by the following command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
@ -461,20 +451,6 @@ Otherwise, you would need to pass ``AHRIMAN_PORT`` and mount container network t
|
||||
|
||||
docker run --privileged --net=host -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Mutli-repository web service
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
Idea is pretty same as to just run web service. However, it is required to run setup commands for each repository, except for one which is specified by ``AHRIMAN_REPOSITORY`` and ``AHRIMAN_ARCHITECTURE`` variables.
|
||||
|
||||
In order to create configuration for additional repositories, the ``AHRIMAN_POSTSETUP_COMMAND`` variable should be used, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -e AHRIMAN_POSTSETUP_COMMAND="ahriman --architecture x86_64 --repository aur-clone-v2 service-setup --build-as-user ahriman --packager 'ahriman bot <ahriman@example.com>'" -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
The command above will also create configuration for the repository named ``aur-clone-v2``.
|
||||
|
||||
Note, however, that the command above is only required in case if the service is going to be used to run subprocesses. Otherwise, everything else (web interface, status, etc) will be handled as usual.
|
||||
|
||||
Non-x86_64 architecture setup
|
||||
-----------------------------
|
||||
@ -1097,7 +1073,7 @@ How to setup web service
|
||||
port = 8080
|
||||
|
||||
#.
|
||||
Start the web service ``systemctl enable --now ahriman-web``.
|
||||
Start the web service ``systemctl enable --now ahriman-web@x86_64-aur-clone``.
|
||||
|
||||
How to enable basic authorization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -1154,7 +1130,7 @@ How to enable basic authorization
|
||||
sudo -u ahriman ahriman user-add -r full my-first-user
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web``.
|
||||
Restart web service ``systemctl restart ahriman-web@x86_64-aur-clone``.
|
||||
|
||||
How to enable OAuth authorization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -1200,7 +1176,7 @@ How to enable OAuth authorization
|
||||
When it will ask for the password leave it blank.
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web``.
|
||||
Restart web service ``systemctl restart ahriman-web@x86_64-aur-clone``.
|
||||
|
||||
How to implement own interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -1323,21 +1299,7 @@ The application uses java concept to log messages, e.g. class ``Application`` im
|
||||
Html customization
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is possible to customize html templates. In order to do so, create files somewhere (refer to Jinja2 documentation and the service source code for available parameters) and prepend ``templates`` with value pointing to this directory.
|
||||
|
||||
In addition, default html templates supports style customization out-of-box. In order to customize style, just put file named ``user-style.jinja2`` to the templates directory.
|
||||
|
||||
Web API extension
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The application loads web views dynamically, so it is possible relatively easy extend its API. In order to do so:
|
||||
|
||||
#. Create view class which is derived from ``ahriman.web.views.base.BaseView`` class.
|
||||
#. Create implementation for this class.
|
||||
#. Put file into ``ahriman.web.views`` package.
|
||||
#. Restart application.
|
||||
|
||||
For more details about implementation and possibilities, kindly refer to module documentation and source code and `aiohttp documentation <https://docs.aiohttp.org/en/stable/>`_.
|
||||
It is possible to customize html templates. In order to do so, create files somewhere (refer to Jinja2 documentation and the service source code for available parameters) and put ``template_path`` to configuration pointing to this directory.
|
||||
|
||||
I did not find my question
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -7,21 +7,21 @@ Features
|
||||
--------
|
||||
|
||||
* Install-configure-forget manager for the very own repository.
|
||||
* Multi architecture and repository support.
|
||||
* Multi-architecture support.
|
||||
* Dependency manager.
|
||||
* VCS packages support.
|
||||
* Official repository support.
|
||||
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
||||
* Various rebuild options with ability to automatically bump package version.
|
||||
* Sign support with gpg (repository, package), multiple packagers support.
|
||||
* Triggers for repository updates, e.g. synchronization to remote services (rsync, S3 and GitHub) and report generation (email, html, telegram).
|
||||
* Triggers for repository updates, e.g. synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram).
|
||||
* Repository status interface with optional authorization and control options.
|
||||
|
||||
Live demos
|
||||
----------
|
||||
|
||||
* `Build status page <https://ahriman-demo.arcanis.me>`_. You can login as ``demo`` user by using ``demo`` password. Note, however, you will not be able to run tasks. `HTTP API documentation <https://ahriman-demo.arcanis.me/api-docs>`_ is also available.
|
||||
* `Repository index <https://repo.arcanis.me/x86_64/index.html>`_.
|
||||
* `Repository index <http://repo.arcanis.me/x86_64/index.html>`_.
|
||||
* `Telegram feed <https://t.me/arcanisrepo>`_.
|
||||
|
||||
Contents
|
||||
@ -34,7 +34,6 @@ Contents
|
||||
configuration
|
||||
command-line
|
||||
faq
|
||||
migration
|
||||
architecture
|
||||
advanced-usage
|
||||
triggers
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
Manual migrations
|
||||
=================
|
||||
|
||||
Normally the most of migrations are handled automatically after application start, however, some upgrades require manual interventions; this document describes them.
|
||||
Normally most of migrations are handled automatically after application start. However, some upgrades require manual interventions; this document describes them.
|
||||
|
||||
Upgrades to breakpoints
|
||||
-----------------------
|
||||
Upgrades from versions
|
||||
----------------------
|
||||
|
||||
To 2.9.0
|
||||
^^^^^^^^
|
||||
@ -13,8 +13,8 @@ This release includes major upgrade for the newest devtools and archlinux reposi
|
||||
|
||||
#. Upgrade to the latest major release of python (3.11) (required by other changes).
|
||||
#. Upgrade devtools to the latest release.
|
||||
#. Backup local settings, ``/etc/ahriman.ini.d/00-setup-overrides.ini`` by default.
|
||||
#. Run setup command (i.e. ``ahriman service-setup``) again with the same arguments as used before. This step can be done manually by moving ``devtools`` configuration (something like ``/usr/share/devtools/pacman-ahriman*.conf``) to new location ``/usr/share/devtools/pacman.conf.d/`` under name ``ahriman.conf``. After that make sure to remove any ``community`` mentions from configurations (e.g. ``/usr/share/devtools/pacman.conf.d/ahriman.conf``, ``/etc/ahriman.ini``) if there were any. The only thing which will change is ``devtools`` configuration.
|
||||
#. Backup your settings, ``/etc/ahriman.ini.d/00-setup-overrides.ini`` by default.
|
||||
#. Run setup command (i.e. ``ahriman service-setup``) again with the same arguments as you used before. This step can be done manually by moving ``devtools`` configuration (something like ``/usr/share/devtools/pacman-ahriman*.conf``) to new location ``/usr/share/devtools/pacman.conf.d/`` under name ``ahriman.conf``. After that make sure to remove any ``community`` mentions from configurations (e.g. ``/usr/share/devtools/pacman.conf.d/ahriman.conf``, ``/etc/ahriman.ini``) if there were any. The only thing which will change is ``devtools`` configuration.
|
||||
#. Remove build chroot as it is incompatible, e.g. ``sudo ahriman service-clean --chroot``.
|
||||
#. Run ``sudo -u ahriman ahriman update --no-aur --no-local --no-manual -yy`` in order to update local databases.
|
||||
|
||||
@ -27,7 +27,7 @@ This release includes paths migration. Unlike usual case, no automatic migration
|
||||
* Path to pacman databases now includes repository name too, it has been changed from ``/var/lib/ahriman/pacman/x86_64`` to ``/var/lib/ahriman/pacman/aur-clone/x86_64``.
|
||||
* Path to repository itself also includes repository name, from ``/var/lib/ahriman/repository/x86_64`` to ``/var/lib/ahriman/repository/aur-clone/x86_64``.
|
||||
|
||||
In order to migrate to the new filesystem tree the following actions are required:
|
||||
In order to migrate to new filesystem tree the following actions are required:
|
||||
|
||||
#.
|
||||
Stop and disable all services, e.g. timer and web service:
|
||||
@ -38,29 +38,18 @@ In order to migrate to the new filesystem tree the following actions are require
|
||||
sudo systemctl disable --now ahriman-web@x86_64
|
||||
|
||||
#.
|
||||
Create directory tree. It can be done by running ``ahriman service-tree-migrate`` subcommand. It performs copying between the old repository tree and the new one. Alternatively directories can be copied by hands.
|
||||
Create directory tree. It can be done by running ``ahriman service-tree-migrate`` subcommand. It performs copying between the old repository tree and the new one. Alternatively you can copy directories by hands.
|
||||
|
||||
#.
|
||||
Edit configuration in case if anything is pointing to the old path, e.g. HTML report generation, in the way in which it will point to the directory inside repository specific one, e.g. ``/var/lib/ahriman/repository/x86_64`` to ``/var/lib/ahriman/repository/aur-clone/x86_64``.
|
||||
Edit configuration in case if anything is pointing to the old path, e.g. HTML report generation, in the way in which it will be pointed to directory inside repository specific one, e.g. ``/var/lib/ahriman/repository/x86_64`` to ``/var/lib/ahriman/repository/aur-clone/x86_64``.
|
||||
|
||||
#.
|
||||
Run setup command (i.e. ``ahriman service-setup``) again with the same arguments as used before. This step can be done manually by editing devtools pacman configuration (``/usr/share/devtools/pacman.conf.d/ahriman-x86_64.conf`` by default) replacing ``Server`` with path to the repository, e.g.:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[aur-clone]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = file:///var/lib/ahriman/repository/aur-clone/x86_64
|
||||
|
||||
In case of manual interventions make sure to remove architecture reference from ``web`` sections (if any) to avoid ambiguity.
|
||||
Make sure to update remote synchronization services if any. Almost all of them rely on current repository tree by default, so you need to setup either redirects or configure to synchronize to the old locations (e.g. ``object_path`` option for S3 synchronization).
|
||||
|
||||
#.
|
||||
Make sure to update remote synchronization services if any. Almost all of them rely on current repository tree by default, so it is required to setup either redirects or configure to synchronize to the old locations (e.g. ``object_path`` option for S3 synchronization).
|
||||
|
||||
#.
|
||||
Enable and start services again. Unit template parameter should include both repository architecture and name, dash separated, e.g. ``x86_64-aur-clone``, where ``x86_64`` is the repository architecture and ``aur-clone`` is the repository name:
|
||||
Enable and start services again. Unit template parameter should include both repository architecture and name, dash separated, e.g. ``x86_64-aur-clone``:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo systemctl enable --now ahriman@x86_64-aur-clone.timer
|
||||
sudo systemctl enable --now ahriman-web
|
||||
sudo systemctl enable --now ahriman-web@x86_64-aur-clone
|
||||
|
||||
@ -81,7 +81,7 @@ Initial setup
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
systemctl enable --now ahriman-web
|
||||
systemctl enable --now ahriman-web@x86_64-aur-clone
|
||||
|
||||
#.
|
||||
Add packages by using ``ahriman package-add {package}`` command:
|
||||
|
||||
@ -95,8 +95,8 @@ As it has been mentioned, our trigger must derive from specific class:
|
||||
|
||||
class SlackReporter(Trigger):
|
||||
|
||||
def __init__(self, repository_id, configuration):
|
||||
Trigger.__init__(self, repository_id, configuration)
|
||||
def __init__(self, architecture, configuration):
|
||||
Trigger.__init__(self, architecture, configuration)
|
||||
self.slack_url = configuration.get("slack", "url")
|
||||
self.channel = configuration.get("slack", "channel")
|
||||
self.username = configuration.get("slack", "username")
|
||||
@ -120,8 +120,8 @@ Obviously you can implement the specified method in class, but for guide purpose
|
||||
|
||||
class SlackReporter(Trigger):
|
||||
|
||||
def __init__(self, repository_id, configuration):
|
||||
Trigger.__init__(self, repository_id, configuration)
|
||||
def __init__(self, architecture, configuration):
|
||||
Trigger.__init__(self, architecture, configuration)
|
||||
self.slack_url = configuration.get("slack", "url")
|
||||
self.channel = configuration.get("slack", "channel")
|
||||
self.username = configuration.get("slack", "username")
|
||||
@ -132,7 +132,7 @@ Obviously you can implement the specified method in class, but for guide purpose
|
||||
Setup the trigger
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
First, put the trigger in any path it can be exported, e.g. by packing the resource into python package (which will lead to import path as ``package.slack_reporter.SlackReporter``) or just put file somewhere it can be accessed by application (e.g. ``/usr/local/lib/slack_reporter.SlackReporter``).
|
||||
First, put the trigger in any path it can be exported, e.g. by packing the resource into python package (which will lead to import path as ``package.slack_reporter.SlackReporter``) or just put file somewhere it can be accessed by application (e.g. ``/usr/local/lib/slack_reporter.py.SlackReporter``).
|
||||
|
||||
After that run application as usual and receive notification in your slack channel.
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Maintainer: Evgeniy Alekseev
|
||||
|
||||
pkgname='ahriman'
|
||||
pkgver=2.12.0
|
||||
pkgver=2.11.0
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH linux ReposItory MANager"
|
||||
arch=('any')
|
||||
@ -46,7 +46,7 @@ package() {
|
||||
python -m installer --destdir="$pkgdir" "dist/$pkgname-$pkgver-py3-none-any.whl"
|
||||
|
||||
# thanks too PEP517, which we all wanted, you need to install data files manually nowadays
|
||||
pushd package && find . \( -type f -or -type l \) -exec install -Dm644 "{}" "$pkgdir/usr/{}" \; && popd
|
||||
pushd package && find . -type f -exec install -Dm644 "{}" "$pkgdir/usr/{}" \; && popd
|
||||
|
||||
# keep usr/share configs as reference and copy them to /etc
|
||||
install -Dm644 "$pkgdir/usr/share/$pkgname/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini"
|
||||
|
||||
@ -5,7 +5,7 @@ post_upgrade() {
|
||||
)
|
||||
|
||||
for v in "${breakpoints[@]}"; do
|
||||
if [[ $(vercmp "$v" "$2") -ne "-1" ]]; then
|
||||
if [[ $(vercmp "$v" "$2") -eq 1 ]]; then
|
||||
"_${v//[.-]/_}_changes"
|
||||
fi
|
||||
done
|
||||
@ -13,30 +13,29 @@ post_upgrade() {
|
||||
|
||||
_2_9_0_1_changes() {
|
||||
cat << EOF
|
||||
It was found that there was an upgrade from old devtools package to the new one, which requires manual intervention:
|
||||
It was found that you were upgrading from old-devtools package to the new one, which requires manual intervention:
|
||||
|
||||
* make sure that devtools are upgraded to the latest release;
|
||||
* merge upstream configuration into local configuration (e.g. by using pacdiff);
|
||||
* run setup command (i.e. ahriman service-setup) with the same arguments as did before;
|
||||
* remove build chroot, e.g.: ahriman service-clean --chroot;
|
||||
* update local databases: ahriman update --no-aur --no-local --no-manual -yy.
|
||||
* merge service configuration if required;
|
||||
* run setup command (i.e. sudo ahriman service-setup) with the same arguments as you did before;
|
||||
* remove build chroot: sudo rm -r /var/lib/ahriman/chroot/ahriman-x86_64/;
|
||||
* update local databases: sudo -u ahriman ahriman update --no-aur --no-local --no-manual -yy;
|
||||
|
||||
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html.
|
||||
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html
|
||||
EOF
|
||||
}
|
||||
|
||||
_2_12_0_1_changes() {
|
||||
cat << EOF
|
||||
Whereas old local tree is still supported it is highly recommended to migrate to the new one:
|
||||
Whereas old tree is still supported it is highly recommended to migrate to the new one:
|
||||
|
||||
* stop and disable all services;
|
||||
* run service-tree-migrate as ahriman user;
|
||||
* edit local configuration to avoid pointing to the old paths;
|
||||
* run setup command (i.e. ahriman service-setup) with the same arguments as did before;
|
||||
* update remote services in order to support new paths (or setup redirects) if any;
|
||||
* enable web and timer services again by using x86_64-aur-clone suffix,
|
||||
where x86_64 is the repository architecture and aur-clone is the repository name.
|
||||
* edit configuration to avoid pointing to the old paths;
|
||||
* update synchronization services in order to support new paths (or setup redirects);
|
||||
* enable web and timer services again by using x86_64-aur-clone suffix, where x86_64 is your architecture and
|
||||
aur-clone is repository name;
|
||||
|
||||
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html.
|
||||
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html
|
||||
EOF
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=ArcH linux ReposItory MANager web server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/ahriman web
|
||||
User=ahriman
|
||||
Group=ahriman
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -1 +0,0 @@
|
||||
ahriman-web.service
|
||||
12
package/lib/systemd/system/ahriman-web@.service
Normal file
12
package/lib/systemd/system/ahriman-web@.service
Normal file
@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=ArcH linux ReposItory MANager web server (%i)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/ahriman --repository-id "%I" web
|
||||
User=ahriman
|
||||
Group=ahriman
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -54,17 +54,14 @@ use_utf = yes
|
||||
|
||||
[email]
|
||||
no_empty_report = yes
|
||||
template = email-index.jinja2
|
||||
templates = /usr/share/ahriman/templates
|
||||
template_path = /usr/share/ahriman/templates/email-index.jinja2
|
||||
ssl = disabled
|
||||
|
||||
[html]
|
||||
template = repo-index.jinja2
|
||||
templates = /usr/share/ahriman/templates
|
||||
template_path = /usr/share/ahriman/templates/repo-index.jinja2
|
||||
|
||||
[telegram]
|
||||
template = telegram-index.jinja2
|
||||
templates = /usr/share/ahriman/templates
|
||||
template_path = /usr/share/ahriman/templates/telegram-index.jinja2
|
||||
|
||||
[upload]
|
||||
target =
|
||||
|
||||
@ -6,8 +6,10 @@
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<!-- Embed elements Elements via Web Component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@stoplight/elements@7.13.7/web-components.min.js" integrity="sha384-aKMPitODat9Dqj3Mva9Rs9jS5Z3KPSW0sFlAOazuULJMFYhAfmORI5SlH9aWIst8" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stoplight/elements@7.13.7/styles.min.css" integrity="sha384-wPzTs1aFAoGq9gqp9NAs2YVTkFXcU2d6Bx11aKRFhVw2B7o1bCwaV9pGHTlUfD2+" crossorigin="anonymous" type="text/css">
|
||||
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css" type="text/css">
|
||||
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>ahriman</title>
|
||||
<title>{{ repository }}</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
|
||||
{% include "utils/style.jinja2" %}
|
||||
{% include "user-style.jinja2" ignore missing %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -14,22 +15,7 @@
|
||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="navbar-brand"><img src="/static/logo.svg" width="30" height="30" alt=""> ahriman</div>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#repositories-navbar-supported-content" aria-controls="repositories-navbar-supported-content" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div id="repositories-navbar-supported-content" class="collapse navbar-collapse">
|
||||
<ul id="repositories" class="nav nav-tabs">
|
||||
{% for repository in repositories %}
|
||||
<li class="nav-item">
|
||||
<a id="{{ repository.id }}-link" class="nav-link" href="#{{ repository.id }}" data-repository="{{ repository.repository }}" data-architecture="{{ repository.architecture }}">{{ repository.repository }} ({{ repository.architecture }})</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<h1 id="badge-repository">ahriman</h1>
|
||||
</div>
|
||||
|
||||
<div id="alert-placeholder" class="toast-container p3 top-0 start-50 translate-middle-x"></div>
|
||||
@ -40,43 +26,42 @@
|
||||
|
||||
{% if not auth.enabled or auth.username is not none %}
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-box"></i><span class="d-none d-sm-inline"> packages</span>
|
||||
<i class="bi bi-box"></i> packages
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button id="package-add-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal" hidden>
|
||||
<button id="package-add-btn" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal" hidden>
|
||||
<i class="bi bi-plus"></i> add
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-update-button" class="btn dropdown-item" onclick="packagesUpdate()" hidden>
|
||||
<button id="package-update-btn" class="btn dropdown-item" onclick="updatePackages()" hidden>
|
||||
<i class="bi bi-play"></i> update
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-rebuild-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal" hidden>
|
||||
<button id="package-rebuild-btn" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal" hidden>
|
||||
<i class="bi bi-arrow-clockwise"></i> rebuild
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-remove-button" class="btn dropdown-item" onclick="packagesRemove()" disabled hidden>
|
||||
<button id="package-remove-btn" class="btn dropdown-item" onclick="removePackages()" disabled hidden>
|
||||
<i class="bi bi-trash"></i> remove
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button id="key-import-button" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
|
||||
<i class="bi bi-key"></i><span class="d-none d-sm-inline"> import key</span>
|
||||
<button id="key-import-btn" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
|
||||
<i class="bi bi-key"></i> import key
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<button type="button" class="btn btn-secondary" onclick="reload()">
|
||||
<i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span>
|
||||
<i class="bi bi-arrow-clockwise"></i> reload
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table id="packages"
|
||||
data-classes="table table-hover"
|
||||
<table id="packages" class="table table-striped table-hover"
|
||||
data-export-options='{"fileName": "packages"}'
|
||||
data-filter-control="true"
|
||||
data-filter-control-visible="false"
|
||||
@ -101,13 +86,13 @@
|
||||
<tr>
|
||||
<th data-checkbox="true"></th>
|
||||
<th data-sortable="true" data-switchable="false" data-field="base" data-filter-control="input" data-filter-control-placeholder="(any base)">package base</th>
|
||||
<th data-sortable="true" data-align="right" data-field="version" data-filter-control="input" data-filter-control-placeholder="(any version)">version</th>
|
||||
<th data-sortable="true" data-field="version" data-filter-control="input" data-filter-control-placeholder="(any version)">version</th>
|
||||
<th data-sortable="true" data-field="packages" data-filter-control="input" data-filter-control-placeholder="(any package)">packages</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="groups" data-filter-control="select" data-filter-data="func:filterListGroups" data-filter-custom-search="filterList" data-filter-control-placeholder="(any group)">groups</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="licenses" data-filter-control="select" data-filter-data="func:filterListLicenses" data-filter-custom-search="filterList" data-filter-control-placeholder="(any license)">licenses</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="packager" data-filter-control="select" data-filter-custom-search="filterContains" data-filter-control-placeholder="(any packager)">packager</th>
|
||||
<th data-sortable="true" data-align="right" data-field="timestamp" data-filter-control="input" data-filter-custom-search="filterDateRange" data-filter-control-placeholder="(any date)">last update</th>
|
||||
<th data-sortable="true" data-align="center" data-cell-style="statusFormat" data-field="status" data-filter-control="select" data-filter-control-placeholder="(any status)">status</th>
|
||||
<th data-sortable="true" data-field="timestamp" data-filter-control="input" data-filter-custom-search="filterDateRange" data-filter-control-placeholder="(any date)">last update</th>
|
||||
<th data-sortable="true" data-cell-style="statusFormat" data-field="status" data-filter-control="select" data-filter-control-placeholder="(any status)">status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
@ -131,7 +116,7 @@
|
||||
{% if auth.enabled %}
|
||||
<ul class="nav">
|
||||
{% if auth.username is none %}
|
||||
<li>{{ auth.control | safe }}</li>
|
||||
<li>{{ auth.control|safe }}</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<form action="/api/v1/logout" method="post">
|
||||
|
||||
@ -8,21 +8,21 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="key-import-fingerprint-input" class="col-2 col-form-label">fingerprint</label>
|
||||
<div class="col-10">
|
||||
<input id="key-import-fingerprint-input" type="text" class="form-control" placeholder="PGP key fingerprint" name="key" required>
|
||||
<label for="key-fingerprint-input" class="col-sm-2 col-form-label">fingerprint</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="key-fingerprint-input" type="text" class="form-control" placeholder="PGP key fingerprint" name="key" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="key-import-server-input" class="col-2 col-form-label">key server</label>
|
||||
<div class="col-10">
|
||||
<input id="key-import-server-input" type="text" class="form-control" placeholder="PGP key server" name="server" value="keyserver.ubuntu.com" required>
|
||||
<label for="key-server-input" class="col-sm-2 col-form-label">key server</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="key-server-input" type="text" class="form-control" placeholder="PGP key server" name="server" value="keyserver.ubuntu.com" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-2"></div>
|
||||
<div class="col-10">
|
||||
<pre class="language-less"><samp id="key-import-body-input" class="pre-scrollable language-less"></samp><button id="key-import-copy-button" type="button" class="btn language-less" onclick="copyPgpKey()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
<div class="col-sm-2"></div>
|
||||
<div class="col-sm-10">
|
||||
<pre class="language-less"><samp id="key-body-input" class="pre-scrollable language-less"></samp><button id="key-copy-btn" type="button" class="btn language-less" onclick="copyPgpKey()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -39,24 +39,24 @@
|
||||
const keyImportModal = $("#key-import-modal");
|
||||
const keyImportForm = $("#key-import-form");
|
||||
keyImportModal.on("hidden.bs.modal", () => {
|
||||
keyImportBodyInput.text("");
|
||||
keyBodyInput.text("");
|
||||
keyImportForm.trigger("reset");
|
||||
});
|
||||
|
||||
const keyImportBodyInput = $("#key-import-body-input");
|
||||
const keyImportCopyButton = $("#key-import-copy-button");
|
||||
const keyBodyInput = $("#key-body-input");
|
||||
const keyCopyButton = $("#key-copy-btn");
|
||||
|
||||
const keyImportFingerprintInput = $("#key-import-fingerprint-input");
|
||||
const keyImportServerInput = $("#key-import-server-input");
|
||||
const keyFingerprintInput = $("#key-fingerprint-input");
|
||||
const keyServerInput = $("#key-server-input");
|
||||
|
||||
async function copyPgpKey() {
|
||||
const logs = keyImportBodyInput.text();
|
||||
await copyToClipboard(logs, keyImportCopyButton);
|
||||
const logs = keyBodyInput.text();
|
||||
await copyToClipboard(logs, keyCopyButton);
|
||||
}
|
||||
|
||||
function fetchPgpKey() {
|
||||
const key = keyImportFingerprintInput.val();
|
||||
const server = keyImportServerInput.val();
|
||||
const key = keyFingerprintInput.val();
|
||||
const server = keyServerInput.val();
|
||||
|
||||
if (key && server) {
|
||||
$.ajax({
|
||||
@ -64,14 +64,14 @@
|
||||
data: {"key": key, "server": server},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => { keyImportBodyInput.text(response.key); },
|
||||
success: response => { keyBodyInput.text(response.key); },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function importPgpKey() {
|
||||
const key = keyImportFingerprintInput.val();
|
||||
const server = keyImportServerInput.val();
|
||||
const key = keyFingerprintInput.val();
|
||||
const server = keyServerInput.val();
|
||||
|
||||
if (key && server) {
|
||||
$.ajax({
|
||||
|
||||
@ -8,18 +8,18 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="login-username" class="col-4 col-form-label">username</label>
|
||||
<div class="col-8">
|
||||
<input id="login-username" type="text" class="form-control" placeholder="enter username" name="username" required>
|
||||
<label for="username" class="col-sm-2 col-form-label">username</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="username" type="text" class="form-control" placeholder="enter username" name="username" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="login-password" class="col-4 col-form-label">password</label>
|
||||
<div class="col-8">
|
||||
<label for="password" class="col-sm-2 col-form-label">password</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input id="login-password" type="password" class="form-control" placeholder="enter password" name="password" required>
|
||||
<input id="password" type="password" class="form-control" placeholder="enter password" name="password" required>
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="showPassword()"><i id="login-show-hide-password-button" class="bi bi-eye"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="showPassword()"><i id="show-hide-password-btn" class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const passwordInput = $("#login-password");
|
||||
const showHidePasswordButton = $("#login-show-hide-password-button");
|
||||
const passwordInput = $("#password");
|
||||
const showHidePasswordButton = $("#show-hide-password-btn");
|
||||
|
||||
function showPassword() {
|
||||
if (passwordInput.attr("type") === "password") {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div id="package-add-modal" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="package-add-form" onsubmit="return false">
|
||||
<div class="modal-header">
|
||||
@ -8,28 +8,12 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="package-add-repository-input" class="col-3 col-form-label">repository</label>
|
||||
<div class="col-9">
|
||||
<select id="package-add-repository-input" class="form-control" required>
|
||||
{% for repository in repositories %}
|
||||
<option value="{{ repository.id }}" data-repository="{{ repository.repository }}" data-architecture="{{ repository.architecture }}">{{ repository.repository }} ({{ repository.architecture }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="package-input" class="col-sm-2 col-form-label">package</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="package-input" type="text" list="known-packages-dlist" autocomplete="off" class="form-control" placeholder="AUR package" name="package" required>
|
||||
<datalist id="known-packages-dlist"></datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="package-add-input" class="col-3 col-form-label">package</label>
|
||||
<div class="col-9">
|
||||
<input id="package-add-input" type="text" list="package-add-known-packages-dlist" autocomplete="off" class="form-control" placeholder="AUR package" required>
|
||||
<datalist id="package-add-known-packages-dlist"></datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<button id="package-add-variable-button" type="button" class="form-control btn btn-light rounded" onclick="packageAddVariableInputCreate()"><i class="bi bi-plus"></i> add environment variable </button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="package-add-variables-div" class="form-group row"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" onclick="packagesAdd()"><i class="bi bi-play"></i> add</button>
|
||||
@ -43,21 +27,14 @@
|
||||
<script>
|
||||
const packageAddModal = $("#package-add-modal");
|
||||
const packageAddForm = $("#package-add-form");
|
||||
packageAddModal.on("shown.bs.modal", () => {
|
||||
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
});
|
||||
packageAddModal.on("hidden.bs.modal", () => {
|
||||
packageAddVariablesDiv.empty();
|
||||
packageAddForm.trigger("reset");
|
||||
});
|
||||
packageAddModal.on("hidden.bs.modal", () => { packageAddForm.trigger("reset"); });
|
||||
|
||||
const packageAddInput = $("#package-add-input");
|
||||
const packageAddRepositoryInput = $("#package-add-repository-input");
|
||||
const packageAddKnownPackagesList = $("#package-add-known-packages-dlist");
|
||||
packageAddInput.keyup(() => {
|
||||
clearTimeout(packageAddInput.data("timeout"));
|
||||
packageAddInput.data("timeout", setTimeout($.proxy(() => {
|
||||
const value = packageAddInput.val();
|
||||
const packageInput = $("#package-input");
|
||||
const knownPackagesList = $("#known-packages-dlist");
|
||||
packageInput.keyup(() => {
|
||||
clearTimeout(packageInput.data("timeout"));
|
||||
packageInput.data("timeout", setTimeout($.proxy(() => {
|
||||
const value = packageInput.val();
|
||||
|
||||
if (value.length >= 3) {
|
||||
$.ajax({
|
||||
@ -72,88 +49,30 @@
|
||||
option.innerText = `${pkg.package} (${pkg.description})`;
|
||||
return option;
|
||||
});
|
||||
packageAddKnownPackagesList.empty().append(options);
|
||||
knownPackagesList.empty().append(options);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, this), 500));
|
||||
});
|
||||
|
||||
const packageAddVariablesDiv = $("#package-add-variables-div");
|
||||
|
||||
function packageAddVariableInputCreate() {
|
||||
const variableInput = document.createElement("div");
|
||||
variableInput.classList.add("input-group");
|
||||
variableInput.classList.add("package-add-variable");
|
||||
|
||||
const variableNameInput = document.createElement("input");
|
||||
variableNameInput.type = "text";
|
||||
variableNameInput.classList.add("form-control");
|
||||
variableNameInput.classList.add("package-add-variable-name");
|
||||
variableNameInput.placeholder = "name";
|
||||
variableNameInput.ariaLabel = "variable name";
|
||||
|
||||
const variableSeparator = document.createElement("span");
|
||||
variableSeparator.classList.add("input-group-text")
|
||||
variableSeparator.textContent = "=";
|
||||
|
||||
const variableValueInput = document.createElement("input");
|
||||
variableValueInput.type = "text";
|
||||
variableValueInput.classList.add("form-control");
|
||||
variableValueInput.classList.add("package-add-variable-value");
|
||||
variableValueInput.placeholder = "value";
|
||||
variableValueInput.ariaLabel = "variable value";
|
||||
|
||||
const variableButtonRemove = document.createElement("button");
|
||||
variableButtonRemove.type = "button";
|
||||
variableButtonRemove.classList.add("btn");
|
||||
variableButtonRemove.classList.add("btn-outline-danger");
|
||||
variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
|
||||
variableButtonRemove.onclick = _ => { return variableInput.remove(); };
|
||||
|
||||
// bring them together
|
||||
variableInput.appendChild(variableNameInput);
|
||||
variableInput.appendChild(variableSeparator);
|
||||
variableInput.appendChild(variableValueInput);
|
||||
variableInput.appendChild(variableButtonRemove);
|
||||
|
||||
packageAddVariablesDiv.append(variableInput);
|
||||
}
|
||||
|
||||
function patchesParse() {
|
||||
const patches = packageAddVariablesDiv.find(".package-add-variable").map((_, element) => {
|
||||
const richElement = $(element);
|
||||
return {
|
||||
key: richElement.find(".package-add-variable-name").val(),
|
||||
value: richElement.find(".package-add-variable-value").val(),
|
||||
};
|
||||
}).filter((_, patch) => patch.key).get();
|
||||
return {patches: patches};
|
||||
}
|
||||
|
||||
function packagesAdd(packages, patches, repository) {
|
||||
packages = packages ?? packageAddInput.val();
|
||||
patches = patches ?? patchesParse();
|
||||
repository = repository ?? getRepositorySelector(packageAddRepositoryInput);
|
||||
|
||||
function packagesAdd() {
|
||||
const packages = packageInput.val();
|
||||
if (packages) {
|
||||
packageAddModal.modal("hide");
|
||||
const onSuccess = update => `Packages ${update} have been added`;
|
||||
const onFailure = error => `Package addition failed: ${error}`;
|
||||
doPackageAction("/api/v1/service/add", [packages], repository, onSuccess, onFailure, patches);
|
||||
doPackageAction("/api/v1/service/add", [packages], onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
|
||||
function packagesRequest(packages, patches) {
|
||||
packages = packages ?? packageAddInput.val();
|
||||
patches = patches ?? patchesParse();
|
||||
const repository = getRepositorySelector(packageAddRepositoryInput);
|
||||
|
||||
function packagesRequest() {
|
||||
const packages = packageInput.val();
|
||||
if (packages) {
|
||||
packageAddModal.modal("hide");
|
||||
const onSuccess = update => `Packages ${update} have been requested`;
|
||||
const onFailure = error => `Package request failed: ${error}`;
|
||||
doPackageAction("/api/v1/service/request", [packages], repository, onSuccess, onFailure, patches);
|
||||
doPackageAction("/api/v1/service/request", [packages], onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -6,49 +6,11 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row mt-2">
|
||||
<div class="col-4 col-lg-1" style="text-align: right">version</div>
|
||||
<div id="package-info-version" class="col-8 col-lg-5"></div>
|
||||
<div class="col-4 col-lg-1" style="text-align: right">packager</div>
|
||||
<div id="package-info-packager" class="col-8 col-lg-5"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mt-2">
|
||||
<div class="col-4 col-lg-1" style="text-align: right">groups</div>
|
||||
<div id="package-info-groups" class="col-8 col-lg-5"></div>
|
||||
<div class="col-4 col-lg-1" style="text-align: right">licenses</div>
|
||||
<div id="package-info-licenses" class="col-8 col-lg-5"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mt-2">
|
||||
<div class="col-4 col-lg-1" style="text-align: right">upstream</div>
|
||||
<div id="package-info-upstream-url" class="col-8 col-lg-5"></div>
|
||||
<div class="col-4 col-lg-1" style="text-align: right">AUR</div>
|
||||
<div id="package-info-aur-url" class="col-8 col-lg-5"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mt-2">
|
||||
<div class="col-4 col-lg-1" style="text-align: right">packages</div>
|
||||
<div id="package-info-packages" class="col-8 col-lg-5"></div>
|
||||
<div class="col-4 col-lg-1" style="text-align: right">depends</div>
|
||||
<div id="package-info-depends" class="col-8 col-lg-5"></div>
|
||||
</div>
|
||||
|
||||
<hr class="col-12">
|
||||
|
||||
<h3>Environment variables</h3>
|
||||
<div id="package-info-variables-div" class="form-group row"></div>
|
||||
|
||||
<hr class="col-12">
|
||||
|
||||
<h3>Build logs</h3>
|
||||
<pre class="language-logs"><samp id="package-info-logs-input" class="pre-scrollable language-logs"></samp><button id="package-info-logs-copy-button" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
<pre class="language-logs"><samp id="package-info-logs-input" class="pre-scrollable language-logs"></samp><button id="logs-copy-btn" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal" hidden><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
|
||||
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal" hidden><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
|
||||
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button>
|
||||
<button type="button" class="btn btn-secondary" onclick="showLogs()"><i class="bi bi-arrow-clockwise"></i> reload</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i> close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -58,110 +20,22 @@
|
||||
const packageInfoModal = $("#package-info-modal");
|
||||
const packageInfoModalHeader = $("#package-info-modal-header");
|
||||
const packageInfo = $("#package-info");
|
||||
packageInfoModal.on("hidden.bs.modal", () => {
|
||||
packageInfoAurUrl.empty();
|
||||
packageInfoDepends.empty();
|
||||
packageInfoGroups.empty();
|
||||
packageInfoLicenses.empty();
|
||||
packageInfoPackager.empty();
|
||||
packageInfoPackages.empty();
|
||||
packageInfoUpstreamUrl.empty();
|
||||
packageInfoVersion.empty();
|
||||
|
||||
packageInfoVariablesDiv.empty();
|
||||
|
||||
packageInfoLogsInput.empty();
|
||||
|
||||
packageInfoModal.trigger("reset");
|
||||
|
||||
hideInfoControls(true);
|
||||
});
|
||||
|
||||
const packageInfoLogsInput = $("#package-info-logs-input");
|
||||
const packageInfoLogsCopyButton = $("#package-info-logs-copy-button");
|
||||
|
||||
const packageInfoAurUrl = $("#package-info-aur-url");
|
||||
const packageInfoDepends = $("#package-info-depends");
|
||||
const packageInfoGroups = $("#package-info-groups");
|
||||
const packageInfoLicenses = $("#package-info-licenses");
|
||||
const packageInfoPackager = $("#package-info-packager");
|
||||
const packageInfoPackages = $("#package-info-packages");
|
||||
const packageInfoUpstreamUrl = $("#package-info-upstream-url");
|
||||
const packageInfoVersion = $("#package-info-version");
|
||||
|
||||
const packageInfoVariablesDiv = $("#package-info-variables-div");
|
||||
const packageInfoLogsCopyButton = $("#logs-copy-btn");
|
||||
|
||||
async function copyLogs() {
|
||||
const logs = packageInfoLogsInput.text();
|
||||
await copyToClipboard(logs, packageInfoLogsCopyButton);
|
||||
}
|
||||
|
||||
function hideInfoControls(hidden) {
|
||||
packageInfoRemoveButton.attr("hidden", hidden);
|
||||
packageInfoUpdateButton.attr("hidden", hidden);
|
||||
}
|
||||
function showLogs(packageBase) {
|
||||
const isPackageBaseSet = packageBase !== undefined;
|
||||
if (isPackageBaseSet)
|
||||
packageInfoModal.data("package", packageBase); // set package base as currently used
|
||||
else
|
||||
packageBase = packageInfoModal.data("package"); // read package base from the current window attribute
|
||||
|
||||
function insertVariable(packageBase, variable) {
|
||||
const variableInput = document.createElement("div");
|
||||
variableInput.classList.add("input-group");
|
||||
|
||||
const variableNameInput = document.createElement("input");
|
||||
variableNameInput.classList.add("form-control");
|
||||
variableNameInput.readOnly = true;
|
||||
variableNameInput.value = variable.key;
|
||||
|
||||
const variableSeparator = document.createElement("span");
|
||||
variableSeparator.classList.add("input-group-text")
|
||||
variableSeparator.textContent = "=";
|
||||
|
||||
const variableValueInput = document.createElement("input");
|
||||
variableValueInput.classList.add("form-control");
|
||||
variableValueInput.readOnly = true;
|
||||
variableValueInput.value = variable.value;
|
||||
|
||||
const variableButtonRemove = document.createElement("button");
|
||||
variableButtonRemove.type = "button";
|
||||
variableButtonRemove.classList.add("btn");
|
||||
variableButtonRemove.classList.add("btn-outline-danger");
|
||||
variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
|
||||
variableButtonRemove.onclick = _ => {
|
||||
$.ajax({
|
||||
url: `/api/v1/packages/${packageBase}/patches/${variable.key}`,
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
success: _ => variableInput.remove(),
|
||||
});
|
||||
};
|
||||
|
||||
// bring them together
|
||||
variableInput.appendChild(variableNameInput);
|
||||
variableInput.appendChild(variableSeparator);
|
||||
variableInput.appendChild(variableValueInput);
|
||||
variableInput.appendChild(variableButtonRemove);
|
||||
|
||||
packageInfoVariablesDiv.append(variableInput);
|
||||
}
|
||||
|
||||
function loadLogs(packageBase, onFailure) {
|
||||
$.ajax({
|
||||
url: `/api/v2/packages/${packageBase}/logs`,
|
||||
data: {
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const logs = response.map(log_record => {
|
||||
return `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`;
|
||||
});
|
||||
packageInfoLogsInput.text(logs.join("\n"));
|
||||
},
|
||||
error: onFailure,
|
||||
});
|
||||
}
|
||||
|
||||
function loadPackage(packageBase, onFailure) {
|
||||
const headerClass = status => {
|
||||
if (status === "pending") return ["bg-warning"];
|
||||
if (status === "building") return ["bg-warning"];
|
||||
@ -171,91 +45,30 @@
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: `/api/v1/packages/${packageBase}`,
|
||||
data: {
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
},
|
||||
url: `/api/v2/packages/${packageBase}/logs`,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const description = response.find(Boolean);
|
||||
const packages = Object.keys(description.package.packages);
|
||||
const aurUrl = description.package.remote.web_url;
|
||||
const upstreamUrls = Object.values(description.package.packages).map(single => single.url);
|
||||
|
||||
packageInfo.text(`${description.package.base} ${description.status.status} at ${new Date(1000 * description.status.timestamp).toISOStringShort()}`);
|
||||
packageInfo.text(`${response.package_base} ${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOString()}`);
|
||||
const logs = response.logs.map(log_record => {
|
||||
const [timestamp, record] = log_record;
|
||||
return `[${new Date(1000 * timestamp).toISOString()}] ${record}`;
|
||||
});
|
||||
packageInfoLogsInput.text(logs.join("\n"));
|
||||
|
||||
packageInfoModalHeader.removeClass();
|
||||
packageInfoModalHeader.addClass("modal-header");
|
||||
headerClass(description.status.status).forEach(clz => packageInfoModalHeader.addClass(clz));
|
||||
headerClass(response.status.status).forEach((clz) => packageInfoModalHeader.addClass(clz));
|
||||
|
||||
packageInfoAurUrl.html(aurUrl ? safeLink(aurUrl, aurUrl, "AUR link").outerHTML : "");
|
||||
packageInfoDepends.html(listToTable(
|
||||
Object.values(description.package.packages)
|
||||
.reduce((accumulator, currentValue) => {
|
||||
return accumulator.concat(currentValue.depends.filter(v => packages.indexOf(v) === -1))
|
||||
.concat(currentValue.make_depends.filter(v => packages.indexOf(v) === -1).map(v => `${v} (make)`))
|
||||
.concat(currentValue.opt_depends.filter(v => packages.indexOf(v) === -1).map(v => `${v} (optional)`));
|
||||
}, [])
|
||||
));
|
||||
packageInfoGroups.html(listToTable(extractListProperties(description.package, "groups")));
|
||||
packageInfoLicenses.html(listToTable(extractListProperties(description.package, "licenses")));
|
||||
packageInfoPackager.text(description.package.packager);
|
||||
packageInfoPackages.html(listToTable(packages));
|
||||
packageInfoUpstreamUrl.html(upstreamUrls.map(url => safeLink(url, url, "upstream link").outerHTML).join("<br>"));
|
||||
packageInfoVersion.text(description.package.version);
|
||||
|
||||
hideInfoControls(false);
|
||||
if (isPackageBaseSet) packageInfoModal.modal("show"); // we don't need to show window again
|
||||
},
|
||||
error: (jqXHR, _, errorThrown) => {
|
||||
hideInfoControls(true);
|
||||
onFailure(jqXHR, null, errorThrown);
|
||||
// show failed modal in case if first time loading
|
||||
if (isPackageBaseSet) {
|
||||
const message = error => `Could not load package ${packageBase} logs: ${error}`;
|
||||
showFailure("Load failure", message, jqXHR, errorThrown);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function loadPatches(packageBase, onFailure) {
|
||||
$.ajax({
|
||||
url: `/api/v1/packages/${packageBase}/patches`,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
packageInfoVariablesDiv.empty();
|
||||
response.map(patch => insertVariable(packageBase, patch));
|
||||
},
|
||||
error: onFailure,
|
||||
});
|
||||
}
|
||||
|
||||
function packageInfoRemove() {
|
||||
const packageBase = packageInfoModal.data("package");
|
||||
if (packageBase) return packagesRemove([packageBase]);
|
||||
}
|
||||
|
||||
function packageInfoUpdate() {
|
||||
const packageBase = packageInfoModal.data("package");
|
||||
if (packageBase) return packagesAdd(packageBase, [], repository);
|
||||
}
|
||||
|
||||
function showPackageInfo(packageBase) {
|
||||
const isPackageBaseSet = packageBase !== undefined;
|
||||
if (isPackageBaseSet)
|
||||
packageInfoModal.data("package", packageBase); // set package base as currently used
|
||||
else
|
||||
packageBase = packageInfoModal.data("package"); // read package base from the current window attribute
|
||||
|
||||
const onFailure = (jqXHR, _, errorThrown) => {
|
||||
if (isPackageBaseSet) {
|
||||
const message = error => `Could not load package ${packageBase} info: ${error}`;
|
||||
showFailure("Load failure", message, jqXHR, errorThrown);
|
||||
}
|
||||
};
|
||||
|
||||
loadPackage(packageBase, onFailure);
|
||||
loadPatches(packageBase, onFailure);
|
||||
loadLogs(packageBase, onFailure);
|
||||
|
||||
if (isPackageBaseSet) packageInfoModal.modal("show");
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div id="package-rebuild-modal" tabindex="-1" role="dialog" class="modal fade">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="package-rebuild-form" onsubmit="return false">
|
||||
<div class="modal-header">
|
||||
@ -8,19 +8,9 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="package-rebuild-repository-input" class="col-3 col-form-label">repository</label>
|
||||
<div class="col-9">
|
||||
<select id="package-rebuild-repository-input" class="form-control" name="repository" required>
|
||||
{% for repository in repositories %}
|
||||
<option value="{{ repository.id }}" data-repository="{{ repository.repository }}" data-architecture="{{ repository.architecture }}">{{ repository.repository }} ({{ repository.architecture }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="package-rebuild-dependency-input" class="col-3 col-form-label">dependency</label>
|
||||
<div class="col-9">
|
||||
<input id="package-rebuild-dependency-input" type="text" class="form-control" placeholder="packages dependency" name="package" required>
|
||||
<label for="dependency-input" class="col-sm-4 col-form-label">dependency</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="dependency-input" type="text" class="form-control" placeholder="packages dependency" name="package" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -35,23 +25,17 @@
|
||||
<script>
|
||||
const packageRebuildModal = $("#package-rebuild-modal");
|
||||
const packageRebuildForm = $("#package-rebuild-form");
|
||||
packageRebuildModal.on("shown.bs.modal", () => {
|
||||
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
|
||||
});
|
||||
packageRebuildModal.on("hidden.bs.modal", () => { packageRebuildForm.trigger("reset"); });
|
||||
|
||||
const packageRebuildDependencyInput = $("#package-rebuild-dependency-input");
|
||||
const packageRebuildRepositoryInput = $("#package-rebuild-repository-input");
|
||||
const dependencyInput = $("#dependency-input");
|
||||
|
||||
function packagesRebuild() {
|
||||
const packages = packageRebuildDependencyInput.val();
|
||||
const repository = getRepositorySelector(packageRebuildRepositoryInput);
|
||||
const packages = dependencyInput.val();
|
||||
if (packages) {
|
||||
packageRebuildModal.modal("hide");
|
||||
const onSuccess = update => `Repository rebuild has been run for packages which depend on ${update}`;
|
||||
const onFailure = error => `Repository rebuild failed: ${error}`;
|
||||
doPackageAction("/api/v1/service/rebuild", [packages], repository, onSuccess, onFailure);
|
||||
doPackageAction("/api/v1/service/rebuild", [packages], onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,24 +1,9 @@
|
||||
<script>
|
||||
const keyImportButton = $("#key-import-button");
|
||||
const packageAddButton = $("#package-add-button");
|
||||
const packageRebuildButton = $("#package-rebuild-button");
|
||||
const packageRemoveButton = $("#package-remove-button");
|
||||
const packageUpdateButton = $("#package-update-button");
|
||||
|
||||
const packageInfoRemoveButton = $("#package-info-remove-button");
|
||||
const packageInfoUpdateButton = $("#package-info-update-button");
|
||||
|
||||
let repository = null;
|
||||
$("#repositories a").on("click", (event) => {
|
||||
const element = event.target;
|
||||
repository = {
|
||||
architecture: element.dataset.architecture,
|
||||
repository: element.dataset.repository,
|
||||
};
|
||||
packageUpdateButton.html(`<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`);
|
||||
$(`#${element.id}`).tab("show");
|
||||
reload();
|
||||
});
|
||||
const keyImportButton = $("#key-import-btn");
|
||||
const packageAddButton = $("#package-add-btn");
|
||||
const packageRebuildButton = $("#package-rebuild-btn");
|
||||
const packageRemoveButton = $("#package-remove-btn");
|
||||
const packageUpdateButton = $("#package-update-btn");
|
||||
|
||||
const table = $("#packages");
|
||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", () => {
|
||||
@ -28,7 +13,7 @@
|
||||
if (0 === cell || "base" === cell) {
|
||||
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
|
||||
table.bootstrapTable(method, {field: "id", values: [data.id]});
|
||||
} else showPackageInfo(data.id);
|
||||
} else showLogs(data.id);
|
||||
});
|
||||
table.on("created-controls.bs.table", () => {
|
||||
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
|
||||
@ -50,18 +35,14 @@
|
||||
});
|
||||
});
|
||||
|
||||
const repositoryBadge = $("#badge-repository");
|
||||
const statusBadge = $("#badge-status");
|
||||
const versionBadge = $("#badge-version");
|
||||
|
||||
function doPackageAction(uri, packages, repository, successText, failureText, data) {
|
||||
const queryParams = $.param({
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
}); // it will never be empty btw
|
||||
|
||||
function doPackageAction(uri, packages, successText, failureText) {
|
||||
$.ajax({
|
||||
url: `${uri}?${queryParams}`,
|
||||
data: JSON.stringify(Object.assign({}, {packages: packages}, data || {})),
|
||||
url: uri,
|
||||
data: JSON.stringify({packages: packages}),
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
success: _ => {
|
||||
@ -74,30 +55,25 @@
|
||||
});
|
||||
}
|
||||
|
||||
function filterListGroups() {
|
||||
return extractDataList(table.bootstrapTable("getData"), "groups");
|
||||
}
|
||||
|
||||
function filterListLicenses() {
|
||||
return extractDataList(table.bootstrapTable("getData"), "licenses");
|
||||
}
|
||||
|
||||
function filterListPackagers() {
|
||||
return extractDataList(table.bootstrapTable("getData"), "packager");
|
||||
}
|
||||
|
||||
function getRepositorySelector(selector) {
|
||||
const selected = selector.find(":selected");
|
||||
return {
|
||||
architecture: selected.data("architecture"),
|
||||
repository: selected.data("repository"),
|
||||
};
|
||||
}
|
||||
|
||||
function getSelection() {
|
||||
return table.bootstrapTable("getSelections").map(row => row.id);
|
||||
}
|
||||
|
||||
function removePackages() {
|
||||
const onSuccess = update => `Packages ${update} have been removed`;
|
||||
const onFailure = error => `Could not remove packages: ${error}`;
|
||||
doPackageAction("/api/v1/service/remove", getSelection(), onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function updatePackages() {
|
||||
const currentSelection = getSelection();
|
||||
const [url, onSuccess] = currentSelection.length === 0
|
||||
? ["/api/v1/service/update", _ => "Repository update has been run"]
|
||||
: ["/api/v1/service/add", update => `Run update for packages ${update}`];
|
||||
const onFailure = error => `Packages update failed: ${error}`;
|
||||
doPackageAction(url, currentSelection, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function hideControls(hidden) {
|
||||
keyImportButton.attr("hidden", hidden);
|
||||
packageAddButton.attr("hidden", hidden);
|
||||
@ -106,24 +82,6 @@
|
||||
packageUpdateButton.attr("hidden", hidden);
|
||||
}
|
||||
|
||||
function packagesRemove(packages) {
|
||||
packages = packages ?? getSelection();
|
||||
const onSuccess = update => `Packages ${update} have been removed`;
|
||||
const onFailure = error => `Could not remove packages: ${error}`;
|
||||
|
||||
doPackageAction("/api/v1/service/remove", packages, repository, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function packagesUpdate() {
|
||||
const currentSelection = getSelection();
|
||||
const [url, onSuccess] = currentSelection.length === 0
|
||||
? ["/api/v1/service/update", _ => `Repository update has been run`]
|
||||
: ["/api/v1/service/add", update => `Run update for packages ${update}`];
|
||||
const onFailure = error => `Packages update failed: ${error}`;
|
||||
|
||||
doPackageAction(url, currentSelection, repository, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
table.bootstrapTable("showLoading");
|
||||
|
||||
@ -137,25 +95,33 @@
|
||||
|
||||
$.ajax({
|
||||
url: "/api/v1/packages",
|
||||
data: {
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const extractListProperties = (description, property) => {
|
||||
return Object.values(description.packages)
|
||||
.map(pkg => pkg[property])
|
||||
.reduce((left, right) => left.concat(right), []);
|
||||
};
|
||||
const listToTable = data => {
|
||||
return Array.from(new Set(data))
|
||||
.sort()
|
||||
.map(entry => 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 ? safeLink(web_url, package_base, package_base).outerHTML : safe(package_base),
|
||||
base: web_url ? `<a href="${safe(web_url)}" title="${safe(package_base)}">${safe(package_base)}</a>` : safe(package_base),
|
||||
version: safe(description.package.version),
|
||||
packager: description.package.packager ? safe(description.package.packager) : "",
|
||||
packages: listToTable(Object.keys(description.package.packages)),
|
||||
groups: listToTable(extractListProperties(description.package, "groups")),
|
||||
licenses: listToTable(extractListProperties(description.package, "licenses")),
|
||||
timestamp: new Date(1000 * description.status.timestamp).toISOStringShort(),
|
||||
timestamp: new Date(1000 * description.status.timestamp).toISOString(),
|
||||
status: description.status.status,
|
||||
};
|
||||
});
|
||||
@ -183,18 +149,15 @@
|
||||
|
||||
$.ajax({
|
||||
url: "/api/v1/status",
|
||||
data: {
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
repositoryBadge.text(`${response.repository} ${response.architecture}`);
|
||||
versionBadge.html(`<i class="bi bi-github"></i> ahriman ${safe(response.version)}`);
|
||||
|
||||
statusBadge
|
||||
.popover("dispose")
|
||||
.attr("data-bs-content", `${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOStringShort()}`)
|
||||
.attr("data-bs-content", `${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOString()}`)
|
||||
.popover();
|
||||
statusBadge.removeClass();
|
||||
statusBadge.addClass("btn");
|
||||
@ -203,12 +166,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function selectRepository() {
|
||||
const fragment = window.location.hash.replace("#", "") || "{{ repositories[0].id }}";
|
||||
const element = $(`#${fragment}-link`);
|
||||
element.click();
|
||||
}
|
||||
|
||||
function statusFormat(value) {
|
||||
const cellClass = status => {
|
||||
if (status === "pending") return "table-warning";
|
||||
@ -220,9 +177,21 @@
|
||||
return {classes: cellClass(value)};
|
||||
}
|
||||
|
||||
function filterListGroups() {
|
||||
return extractDataList(table.bootstrapTable("getData"), "groups");
|
||||
}
|
||||
|
||||
function filterListLicenses() {
|
||||
return extractDataList(table.bootstrapTable("getData"), "licenses");
|
||||
}
|
||||
|
||||
function filterListPackagers() {
|
||||
return extractDataList(table.bootstrapTable("getData"), "packager");
|
||||
}
|
||||
|
||||
$(() => {
|
||||
table.bootstrapTable({});
|
||||
statusBadge.popover();
|
||||
selectRepository();
|
||||
reload();
|
||||
});
|
||||
</script>
|
||||
@ -7,7 +7,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
{% include "utils/style.jinja2" %}
|
||||
{% include "user-style.jinja2" ignore missing %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -5,8 +5,9 @@
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
|
||||
{% include "utils/style.jinja2" %}
|
||||
{% include "user-style.jinja2" ignore missing %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
{% include "utils/style.jinja2" %}
|
||||
{% include "user-style.jinja2" ignore missing %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -30,8 +29,7 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<table id="packages"
|
||||
data-classes="table table-hover"
|
||||
<table id="packages" class="table table-striped table-hover"
|
||||
data-export-options='{"fileName": "packages"}'
|
||||
data-filter-control="true"
|
||||
data-filter-control-visible="false"
|
||||
@ -54,16 +52,16 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
||||
<thead class="table-primary">
|
||||
<tr>
|
||||
<th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th>
|
||||
<th data-sortable="true" data-align="right" data-field="version" data-filter-control="input" data-filter-control-placeholder="(any version)">version</th>
|
||||
<th data-sortable="true" data-field="version" data-filter-control="input" data-filter-control-placeholder="(any version)">version</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="architecture" data-filter-control="select" data-filter-control-placeholder="(any arch)">architecture</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="description" data-filter-control="input" data-filter-control-placeholder="(any description)">description</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="url">upstream url</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="licenses" data-filter-control="select" data-filter-data="func:filterListLicenses" data-filter-custom-search="filterList" data-filter-control-placeholder="(any license)">licenses</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="groups" data-filter-control="select" data-filter-data="func:filterListGroups" data-filter-custom-search="filterList" data-filter-control-placeholder="(any group)">groups</th>
|
||||
<th data-sortable="true" data-visible="false" data-field="depends" data-filter-control="select" data-filter-data="func:filterListDepends" data-filter-custom-search="filterList" data-filter-control-placeholder="(any depends)">depends</th>
|
||||
<th data-sortable="true" data-align="right" data-field="archive_size">archive size</th>
|
||||
<th data-sortable="true" data-align="right" data-field="installed_size">installed size</th>
|
||||
<th data-sortable="true" data-align="right" data-field="timestamp" data-filter-control="input" data-filter-custom-search="filterDateRange" data-filter-control-placeholder="(any date)">build date</th>
|
||||
<th data-sortable="true" data-field="archive_size">archive size</th>
|
||||
<th data-sortable="true" data-field="installed_size">installed size</th>
|
||||
<th data-sortable="true" data-field="timestamp" data-filter-control="input" data-filter-custom-search="filterDateRange" data-filter-control-placeholder="(any date)">build date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1019 2546 c-51 -18 -92 -49 -136 -103 -34 -41 -83 -73 -112 -73 -6
|
||||
0 4 22 20 49 17 27 30 54 28 59 -7 17 -153 -102 -176 -143 -13 -23 -30 -40
|
||||
-40 -40 -15 0 -18 9 -18 61 0 49 6 73 33 125 l33 64 -42 -40 c-50 -49 -82 -97
|
||||
-98 -150 -31 -102 -43 -125 -77 -146 -19 -11 -48 -42 -65 -67 -17 -26 -62 -76
|
||||
-100 -112 -69 -66 -69 -66 -69 -120 0 -30 5 -62 10 -70 5 -8 10 -10 10 -4 0 7
|
||||
21 35 46 63 69 77 154 95 154 33 0 -42 -27 -126 -50 -155 l-21 -26 25 -7 c34
|
||||
-8 63 10 86 56 27 53 74 105 127 142 42 29 44 32 38 68 -6 33 -3 40 22 60 25
|
||||
20 36 22 96 17 83 -7 121 5 137 43 7 16 19 31 28 34 10 3 -9 3 -40 1 -65 -6
|
||||
-84 8 -48 37 34 27 118 24 180 -7 57 -29 115 -32 150 -8 l24 17 -24 6 c-14 3
|
||||
-27 14 -28 24 -4 17 1 18 46 13 58 -7 118 -34 147 -67 38 -44 107 -80 149 -80
|
||||
36 1 38 2 21 12 -41 23 -115 108 -115 133 0 13 58 -27 94 -64 42 -43 86 -58
|
||||
133 -46 28 7 28 7 6 18 -13 5 -23 19 -23 29 0 14 6 19 23 16 17 -2 32 -23 62
|
||||
-85 48 -99 63 -115 123 -141 97 -41 101 -40 50 16 -54 60 -80 113 -76 149 l3
|
||||
26 58 -47 c61 -50 87 -89 87 -131 0 -43 27 -93 59 -109 38 -20 37 -21 36 11 0
|
||||
45 24 38 47 -14 19 -41 22 -63 20 -146 -2 -91 0 -103 25 -148 45 -83 54 -83
|
||||
47 2 -6 74 11 155 32 153 20 -2 47 -101 48 -179 1 -65 -3 -88 -22 -122 -24
|
||||
-46 -28 -98 -10 -138 14 -30 24 -32 31 -5 8 30 37 25 37 -6 0 -53 -23 -113
|
||||
-62 -160 -52 -63 -58 -81 -58 -167 l0 -72 35 65 c40 75 98 135 103 107 4 -19
|
||||
-37 -114 -69 -158 -11 -16 -49 -46 -85 -66 -64 -37 -94 -76 -94 -122 0 -20 1
|
||||
-21 24 -6 19 13 25 13 35 2 17 -21 -15 -43 -73 -51 -85 -11 -126 -26 -164 -61
|
||||
-35 -31 -80 -95 -67 -95 3 0 29 12 57 26 52 26 168 45 168 28 0 -12 -92 -91
|
||||
-123 -105 -20 -9 -34 -8 -71 6 -52 20 -104 14 -130 -14 -17 -19 -17 -20 6 -23
|
||||
12 -2 24 -11 26 -20 4 -22 -37 -23 -189 -7 -99 11 -104 10 -151 -12 -26 -13
|
||||
-48 -27 -48 -30 0 -3 25 -16 55 -28 30 -13 55 -26 55 -31 0 -8 -73 -20 -119
|
||||
-20 -44 0 -99 20 -130 46 -37 31 -87 46 -124 38 l-27 -6 22 -18 c16 -13 20
|
||||
-22 12 -29 -18 -19 -125 42 -174 100 -45 53 -95 82 -165 95 l-40 7 52 -41 c96
|
||||
-76 114 -128 31 -86 -105 52 -120 69 -147 172 -9 31 -21 48 -44 62 -41 25 -61
|
||||
26 -49 2 5 -9 7 -23 5 -29 -10 -27 -32 -11 -57 39 -21 43 -26 66 -26 132 0 71
|
||||
-4 87 -29 131 -32 55 -80 106 -67 71 21 -58 18 -206 -4 -206 -5 0 -30 43 -56
|
||||
95 l-48 96 17 47 c21 57 21 89 2 122 -14 25 -14 25 -15 3 0 -14 -6 -23 -16
|
||||
-23 -14 0 -16 8 -10 53 4 28 18 70 31 92 14 22 31 55 40 73 18 37 32 152 19
|
||||
152 -5 0 -17 -21 -28 -46 -18 -45 -61 -104 -75 -104 -4 0 -4 32 0 72 6 55 16
|
||||
83 40 122 24 37 34 66 37 108 6 75 -1 91 -22 51 -16 -32 -48 -44 -61 -23 -8
|
||||
12 22 69 40 76 19 8 41 43 65 105 l21 52 -29 -29 c-16 -16 -48 -40 -71 -54
|
||||
-23 -14 -56 -45 -72 -69 -28 -43 -29 -47 -29 -175 0 -186 -12 -220 -43 -122
|
||||
-10 29 -15 62 -11 72 4 14 -2 11 -20 -9 -31 -33 -33 -65 -7 -115 21 -40 38
|
||||
-112 27 -112 -16 0 -58 35 -71 59 l-14 26 -1 -37 c0 -24 13 -63 36 -108 39
|
||||
-76 51 -155 24 -155 -8 0 -20 16 -27 35 l-11 35 -7 -29 c-11 -40 11 -127 43
|
||||
-175 15 -22 56 -72 90 -111 70 -78 66 -86 -28 -51 -71 27 -60 7 30 -54 71 -48
|
||||
106 -93 86 -112 -4 -4 -46 12 -93 37 -110 58 -109 58 -87 20 29 -49 70 -81
|
||||
188 -145 114 -63 148 -94 126 -120 -13 -16 -52 -6 -82 22 -28 25 -21 0 11 -44
|
||||
45 -63 100 -83 259 -99 154 -15 221 -29 272 -54 l35 -18 -35 -6 c-56 -10 -164
|
||||
-26 -175 -26 -5 -1 15 -10 45 -21 62 -23 124 -27 255 -15 72 6 94 5 109 -7 28
|
||||
-21 11 -37 -42 -38 l-47 -1 40 -19 c63 -31 153 -22 244 25 80 41 166 66 224
|
||||
66 l38 0 -26 -35 c-15 -19 -48 -48 -73 -66 l-47 -31 72 4 c71 4 72 4 130 59
|
||||
66 64 104 86 160 95 27 4 42 2 50 -8 16 -19 4 -34 -43 -58 l-39 -19 54 -1 c71
|
||||
0 129 28 181 88 22 26 68 80 102 120 35 41 74 89 87 108 13 19 26 33 28 31 2
|
||||
-2 -3 -46 -12 -98 -20 -128 -20 -128 26 -73 52 63 64 91 83 196 18 108 39 150
|
||||
69 146 23 -3 27 -20 14 -70 -11 -42 18 -25 49 28 34 58 41 139 20 222 -21 84
|
||||
-21 83 19 172 19 43 38 99 41 125 l7 46 38 -68 39 -68 0 88 c0 80 -3 93 -35
|
||||
157 -41 80 -47 140 -15 140 11 0 26 -12 34 -27 15 -27 15 -27 16 35 0 54 -4
|
||||
70 -29 105 -16 23 -49 57 -74 76 -56 42 -111 153 -122 248 l-7 63 58 -26 c32
|
||||
-14 71 -35 86 -45 40 -29 35 -10 -18 60 -34 45 -65 72 -117 103 -118 69 -153
|
||||
102 -141 132 9 22 25 20 68 -9 42 -28 43 -26 14 26 -36 65 -136 119 -217 119
|
||||
-48 0 -233 88 -211 100 8 4 44 11 80 14 129 14 130 15 58 47 -62 29 -64 29
|
||||
-198 23 -121 -6 -138 -5 -153 11 -25 24 -6 43 46 47 l42 3 -35 20 c-25 13 -53
|
||||
19 -98 19 -54 0 -74 -6 -143 -42 -76 -39 -139 -54 -139 -33 0 6 45 32 100 60
|
||||
l99 50 -55 7 c-76 9 -111 2 -214 -46 -99 -46 -155 -51 -155 -15 0 14 12 24 44
|
||||
36 40 14 42 16 20 22 -37 10 -80 8 -120 -7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.7 KiB |
@ -1,5 +1,4 @@
|
||||
{#simplified version of full report#}
|
||||
<b>{{ repository }} update</b>
|
||||
{% for package in packages %}
|
||||
<a href="{{ link_path }}/{{ package.filename }}">{{ package.name }}</a> {{ package.version }}
|
||||
{% endfor %}
|
||||
<a href="{{ link_path }}/{{ package.filename }}">{{ package.name }}</a> {{ package.version }}{% endfor %}
|
||||
@ -1,4 +1,4 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.0/dist/jquery.min.js" integrity="sha384-NXgwF8Kv9SSAr+jemKKcbvQsz+teULH/a5UNJvZc6kP47hZgl62M1vGnw6gHQhb1" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js" integrity="sha384-8hHkOkbWN1TLWwet/jpbJ0zbx3FJDeYJgQ8dX1mRrv/vfCfHCqFSFZYCgaMML3z9" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.min.js" integrity="sha384-u4eJN1VWrTf/FnYYQJo2kqJyVxEQf5UmWY4iUcNAoLenOEtEuCkfwc5bKvZOWBi5" crossorigin="anonymous" type="application/javascript"></script>
|
||||
@ -8,12 +8,14 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" integrity="sha384-IazMVNyYoUNx6357fWJoqtHYUWWCNHIXxFVtbpVgvImQNWuRP2WbHPaIb3QF8j97" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/bootstrap-table.min.js" integrity="sha384-GVLHfbEvuGA/RFiQ3MK2ClEJkWYJXABg55t9LpoDPZFGIsSq8xhFlQydm5poV2jW" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/export/bootstrap-table-export.min.js" integrity="sha384-g9OAB1Moamcy8+l1Q/tajHlMf6NTkS79ehKLTYbA80aQRbRhFCjrSuezv+FE2Kwe" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/resizable/bootstrap-table-resizable.js" integrity="sha384-wd8Vc6Febikdnsnk9vthRWRvMwffw246vhqiqNO3aSNe1maTEA07Vh3zAQiSyDji" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" integrity="sha384-NIqcjpr/3eZI1iNzz7hgT5rgp70qFUzkZffeCgVva9gi80B5vqcm7gn+8QvlWxko" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.21.4/dist/extensions/export/bootstrap-table-export.min.js" integrity="sha384-jeldDadm+qM2RwGER3qVqxFgWVpAEJ7Jie+0rlYj8ni3KkQA654T8TSXDtol022X" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.21.4/dist/extensions/resizable/bootstrap-table-resizable.js" integrity="sha384-wd8Vc6Febikdnsnk9vthRWRvMwffw246vhqiqNO3aSNe1maTEA07Vh3zAQiSyDji" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.21.4/dist/extensions/filter-control/bootstrap-table-filter-control.js" integrity="sha384-B6xNXlSOaOFxjlKo9OW3htbox+9/DcaEcjPPEi1+pTMwH5Tzc/s2wNTYriHz7Tb8" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script>
|
||||
async function copyToClipboard(text, button) {
|
||||
@ -34,17 +36,19 @@
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function safe(string) {
|
||||
return String(string)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function extractDataList(data, column) {
|
||||
const elements = data.flatMap(row => row[column].split("<br>")).filter(v => v); // remove empty elements from array
|
||||
return Array.from(new Set(elements)).sort();
|
||||
}
|
||||
|
||||
function extractListProperties(description, property) {
|
||||
return Object.values(description.packages)
|
||||
.map(pkg => pkg[property])
|
||||
.reduce((left, right) => left.concat(right), []);
|
||||
}
|
||||
|
||||
function filterContains(text, value) {
|
||||
return value.includes(text.toLowerCase().trim());
|
||||
}
|
||||
@ -55,7 +59,7 @@
|
||||
const [minDate, maxDate] = text.split(" - ");
|
||||
const buildDate = asOfStartOfDay(new Date(value));
|
||||
|
||||
return (buildDate >= new Date(minDate)) && (buildDate <= new Date(maxDate));
|
||||
return (buildDate >= asOfStartOfDay(new Date(minDate))) && (buildDate <= asOfStartOfDay(new Date(maxDate)));
|
||||
}
|
||||
|
||||
function filterList(index, value, field, data) {
|
||||
@ -63,33 +67,4 @@
|
||||
// the library removes all symbols from string, so it is just string
|
||||
return value.includes(dataList[index].toLowerCase());
|
||||
}
|
||||
|
||||
function listToTable(data) {
|
||||
return Array.from(new Set(data))
|
||||
.sort()
|
||||
.map(entry => safe(entry))
|
||||
.join("<br>");
|
||||
}
|
||||
|
||||
function safe(string) {
|
||||
return String(string)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
// because I'm tired of safe element generation
|
||||
function safeLink(url, text, title) {
|
||||
const element = document.createElement("a");
|
||||
element.href = url;
|
||||
element.innerText = text;
|
||||
if (title) element.title = title;
|
||||
return element;
|
||||
}
|
||||
|
||||
Date.prototype.toISOStringShort = function() {
|
||||
const pad = number => String(number).padStart(2, "0");
|
||||
return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" integrity="sha384-4LISF5TTJX/fLmGSxO53rV4miRxdg84mZsxmO8Rx5jGtp/LbrixFETvWa5a6sESd" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css" integrity="sha384-LrVLJJYk9OiJmjNDakUBU7kS9qCT8wk1j2OU7ncpsfB3QS37UPdkCuq3ZD1MugNY" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/bootstrap-table.min.css" integrity="sha384-sN3NwxbjH33ZidqZnPmX+nQ5IF+LoiI7HvZSoZj5wGacmu0/q4RJfsN0xqN+LIa5" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.21.4/dist/bootstrap-table.min.css" integrity="sha384-pTEAhytv7JmEG2D7qiW5gY0lI5EKZ9n3CNmj6Qp+U3qhnmH2qnnN9KJbVwbtMHN0" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" integrity="sha384-1sLxvR8mXzjhvFY9f8mzXl97DNLepeZ0PnRiMMdm/rQsKjsrPZPJxYle2wwT2PMg" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.css" integrity="sha384-4Glx18jZ0Un+yDG6KUpYJ/af8hkssJ02jRASuFv23gfCl0mTXaVMPI9cB4cn3GvE" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.21.4/dist/extensions/filter-control/bootstrap-table-filter-control.css" integrity="sha384-4Glx18jZ0Un+yDG6KUpYJ/af8hkssJ02jRASuFv23gfCl0mTXaVMPI9cB4cn3GvE" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.2/dist/cosmo/bootstrap.min.css" integrity="sha384-RfV5VNj9uqyOdZbN0hFNmoq56291KK2Y4iKhoRAbcfBfjYlpasjxK6TefPjxiAiN" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.2.3/dist/cosmo/bootstrap.min.css" integrity="sha384-P1PBFVifKf1Ww0gS5B8A0siIeDpcFd4uU7S68LA1XMdE0R+y1WN3DR4HcLc9csRC" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.css" integrity="sha384-zLkQsiLfAQqGeIJeKLC+rcCR1YoYaQFLCL7cLDUoKE1ajKJzySpjzWGfYS2vjSG+" crossorigin="anonymous" type="text/css">
|
||||
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
# AUTOMATICALLY GENERATED by `shtab`
|
||||
|
||||
_shtab_ahriman_subparsers=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
_shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
|
||||
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '--wait-timeout' '-V' '--version')
|
||||
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
|
||||
_shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
|
||||
_shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_help_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_help_updates_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_help_version_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_version_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_package_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
|
||||
_shtab_ahriman_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
|
||||
_shtab_ahriman_package_update_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
|
||||
_shtab_ahriman_package_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username')
|
||||
_shtab_ahriman_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username')
|
||||
_shtab_ahriman_package_update_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username')
|
||||
_shtab_ahriman_package_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_package_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status')
|
||||
@ -50,17 +50,14 @@ _shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--depend
|
||||
_shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_service_config_option_strings=('-h' '--help' '--info' '--no-info' '--secure' '--no-secure')
|
||||
_shtab_ahriman_config_option_strings=('-h' '--help' '--info' '--no-info' '--secure' '--no-secure')
|
||||
_shtab_ahriman_repo_config_option_strings=('-h' '--help' '--info' '--no-info' '--secure' '--no-secure')
|
||||
_shtab_ahriman_service_config_option_strings=('-h' '--help' '--secure' '--no-secure')
|
||||
_shtab_ahriman_config_option_strings=('-h' '--help' '--secure' '--no-secure')
|
||||
_shtab_ahriman_repo_config_option_strings=('-h' '--help' '--secure' '--no-secure')
|
||||
_shtab_ahriman_service_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_repo_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_service_key_import_option_strings=('-h' '--help' '--key-server')
|
||||
_shtab_ahriman_key_import_option_strings=('-h' '--help' '--key-server')
|
||||
_shtab_ahriman_service_repositories_option_strings=('-h' '--help' '--id-only' '--no-id-only')
|
||||
_shtab_ahriman_service_run_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_run_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_service_setup_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_init_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_repo_init_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
@ -76,7 +73,7 @@ _shtab_ahriman_web_option_strings=('-h' '--help')
|
||||
|
||||
|
||||
|
||||
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
|
||||
_shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
|
||||
_shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
|
||||
@ -135,11 +132,11 @@ _shtab_ahriman_search__e_nargs=0
|
||||
_shtab_ahriman_search___exit_code_nargs=0
|
||||
_shtab_ahriman_search___info_nargs=0
|
||||
_shtab_ahriman_search___no_info_nargs=0
|
||||
_shtab_ahriman_help__h_nargs=0
|
||||
_shtab_ahriman_help___help_nargs=0
|
||||
_shtab_ahriman_help_commands_unsafe_pos_0_nargs=*
|
||||
_shtab_ahriman_help_commands_unsafe__h_nargs=0
|
||||
_shtab_ahriman_help_commands_unsafe___help_nargs=0
|
||||
_shtab_ahriman_help__h_nargs=0
|
||||
_shtab_ahriman_help___help_nargs=0
|
||||
_shtab_ahriman_help_updates__h_nargs=0
|
||||
_shtab_ahriman_help_updates___help_nargs=0
|
||||
_shtab_ahriman_help_updates__e_nargs=0
|
||||
@ -403,20 +400,14 @@ _shtab_ahriman_repo_clean___pacman_nargs=0
|
||||
_shtab_ahriman_repo_clean___no_pacman_nargs=0
|
||||
_shtab_ahriman_service_config__h_nargs=0
|
||||
_shtab_ahriman_service_config___help_nargs=0
|
||||
_shtab_ahriman_service_config___info_nargs=0
|
||||
_shtab_ahriman_service_config___no_info_nargs=0
|
||||
_shtab_ahriman_service_config___secure_nargs=0
|
||||
_shtab_ahriman_service_config___no_secure_nargs=0
|
||||
_shtab_ahriman_config__h_nargs=0
|
||||
_shtab_ahriman_config___help_nargs=0
|
||||
_shtab_ahriman_config___info_nargs=0
|
||||
_shtab_ahriman_config___no_info_nargs=0
|
||||
_shtab_ahriman_config___secure_nargs=0
|
||||
_shtab_ahriman_config___no_secure_nargs=0
|
||||
_shtab_ahriman_repo_config__h_nargs=0
|
||||
_shtab_ahriman_repo_config___help_nargs=0
|
||||
_shtab_ahriman_repo_config___info_nargs=0
|
||||
_shtab_ahriman_repo_config___no_info_nargs=0
|
||||
_shtab_ahriman_repo_config___secure_nargs=0
|
||||
_shtab_ahriman_repo_config___no_secure_nargs=0
|
||||
_shtab_ahriman_service_config_validate__h_nargs=0
|
||||
@ -435,16 +426,6 @@ _shtab_ahriman_service_key_import__h_nargs=0
|
||||
_shtab_ahriman_service_key_import___help_nargs=0
|
||||
_shtab_ahriman_key_import__h_nargs=0
|
||||
_shtab_ahriman_key_import___help_nargs=0
|
||||
_shtab_ahriman_service_repositories__h_nargs=0
|
||||
_shtab_ahriman_service_repositories___help_nargs=0
|
||||
_shtab_ahriman_service_repositories___id_only_nargs=0
|
||||
_shtab_ahriman_service_repositories___no_id_only_nargs=0
|
||||
_shtab_ahriman_service_run_pos_0_nargs=+
|
||||
_shtab_ahriman_service_run__h_nargs=0
|
||||
_shtab_ahriman_service_run___help_nargs=0
|
||||
_shtab_ahriman_run_pos_0_nargs=+
|
||||
_shtab_ahriman_run__h_nargs=0
|
||||
_shtab_ahriman_run___help_nargs=0
|
||||
_shtab_ahriman_service_setup__h_nargs=0
|
||||
_shtab_ahriman_service_setup___help_nargs=0
|
||||
_shtab_ahriman_service_setup___generate_salt_nargs=0
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
.TH AHRIMAN "1" "2023\-11\-06" "ahriman" "Generated Python Manual"
|
||||
.TH AHRIMAN "1" "2023\-09\-02" "ahriman" "Generated Python Manual"
|
||||
.SH NAME
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
.B ahriman
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [--wait-timeout WAIT_TIMEOUT] [-V] {aur-search,search,help-commands-unsafe,help,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-repositories,service-run,run,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ...
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [--wait-timeout WAIT_TIMEOUT] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ...
|
||||
.SH DESCRIPTION
|
||||
ArcH linux ReposItory MANager
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-a\fR \fI\,ARCHITECTURE\/\fR, \fB\-\-architecture\fR \fI\,ARCHITECTURE\/\fR
|
||||
filter by target architecture
|
||||
target architectures. For several subcommands it can be used multiple times
|
||||
|
||||
.TP
|
||||
\fB\-c\fR \fI\,CONFIGURATION\/\fR, \fB\-\-configuration\fR \fI\,CONFIGURATION\/\fR
|
||||
@ -38,7 +38,7 @@ force enable or disable reporting to web service
|
||||
|
||||
.TP
|
||||
\fB\-r\fR \fI\,REPOSITORY\/\fR, \fB\-\-repository\fR \fI\,REPOSITORY\/\fR
|
||||
filter by target repository
|
||||
target repository. For several subcommands it can be used multiple times
|
||||
|
||||
.TP
|
||||
\fB\-\-unsafe\fR
|
||||
@ -59,12 +59,12 @@ COMMAND
|
||||
\fBahriman\fR \fI\,aur\-search\/\fR
|
||||
search for package
|
||||
.TP
|
||||
\fBahriman\fR \fI\,help\-commands\-unsafe\/\fR
|
||||
list unsafe commands
|
||||
.TP
|
||||
\fBahriman\fR \fI\,help\/\fR
|
||||
show help message
|
||||
.TP
|
||||
\fBahriman\fR \fI\,help\-commands\-unsafe\/\fR
|
||||
list unsafe commands
|
||||
.TP
|
||||
\fBahriman\fR \fI\,help\-updates\/\fR
|
||||
check for service updates
|
||||
.TP
|
||||
@ -155,12 +155,6 @@ validate system configuration
|
||||
\fBahriman\fR \fI\,service\-key\-import\/\fR
|
||||
import PGP key
|
||||
.TP
|
||||
\fBahriman\fR \fI\,service\-repositories\/\fR
|
||||
show repositories
|
||||
.TP
|
||||
\fBahriman\fR \fI\,service\-run\/\fR
|
||||
run multiple commands
|
||||
.TP
|
||||
\fBahriman\fR \fI\,service\-setup\/\fR
|
||||
initial service configuration
|
||||
.TP
|
||||
@ -207,6 +201,15 @@ show additional package information
|
||||
sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted
|
||||
by name
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help'\/\fR
|
||||
usage: ahriman help [\-h] [command]
|
||||
|
||||
show help message for application or command and exit
|
||||
|
||||
.TP
|
||||
\fBcommand\fR
|
||||
show help message for specific command
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR
|
||||
usage: ahriman help\-commands\-unsafe [\-h] [command ...]
|
||||
|
||||
@ -217,15 +220,6 @@ list unsafe commands as defined in default args
|
||||
instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1
|
||||
otherwise
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help'\/\fR
|
||||
usage: ahriman help [\-h] [command]
|
||||
|
||||
show help message for application or command and exit
|
||||
|
||||
.TP
|
||||
\fBcommand\fR
|
||||
show help message for specific command
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help\-updates'\/\fR
|
||||
usage: ahriman help\-updates [\-h] [\-e]
|
||||
|
||||
@ -243,7 +237,7 @@ print application and its dependencies versions
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-add'\/\fR
|
||||
usage: ahriman package\-add [\-h] [\-\-dependencies | \-\-no\-dependencies] [\-e] [\-\-increment | \-\-no\-increment] [\-n] [\-y]
|
||||
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE]
|
||||
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME]
|
||||
package [package ...]
|
||||
|
||||
add existing or new package to the build queue
|
||||
@ -281,10 +275,6 @@ explicitly specify the package source for this command
|
||||
\fB\-u\fR \fI\,USERNAME\/\fR, \fB\-\-username\fR \fI\,USERNAME\/\fR
|
||||
build as user
|
||||
|
||||
.TP
|
||||
\fB\-v\fR \fI\,VARIABLE\/\fR, \fB\-\-variable\fR \fI\,VARIABLE\/\fR
|
||||
apply specified makepkg variables to the next build
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR
|
||||
usage: ahriman package\-remove [\-h] package [package ...]
|
||||
|
||||
@ -674,23 +664,11 @@ clear directory with built packages
|
||||
clear directory with pacman local database cache
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-config'\/\fR
|
||||
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
|
||||
usage: ahriman service\-config [\-h] [\-\-secure | \-\-no\-secure]
|
||||
|
||||
dump configuration for the specified architecture
|
||||
|
||||
.TP
|
||||
\fBsection\fR
|
||||
filter settings by section
|
||||
|
||||
.TP
|
||||
\fBkey\fR
|
||||
filter settings by key
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman service\-config'\/\fR
|
||||
.TP
|
||||
\fB\-\-info\fR, \fB\-\-no\-info\fR
|
||||
show additional information, e.g. configuration files
|
||||
|
||||
.TP
|
||||
\fB\-\-secure\fR, \fB\-\-no\-secure\fR
|
||||
hide passwords and secrets from output
|
||||
@ -719,25 +697,6 @@ PGP key to import from public server
|
||||
\fB\-\-key\-server\fR \fI\,KEY_SERVER\/\fR
|
||||
key server for key import
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-repositories'\/\fR
|
||||
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
|
||||
|
||||
list all available repositories
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman service\-repositories'\/\fR
|
||||
.TP
|
||||
\fB\-\-id\-only\fR, \fB\-\-no\-id\-only\fR
|
||||
show machine readable identifier instead
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-run'\/\fR
|
||||
usage: ahriman service\-run [\-h] command [command ...]
|
||||
|
||||
run multiple commands on success run of the previous command
|
||||
|
||||
.TP
|
||||
\fBcommand\fR
|
||||
command to be run (quoted) without ``ahriman``
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-setup'\/\fR
|
||||
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
|
||||
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
|
||||
|
||||
@ -52,14 +52,11 @@ _shtab_ahriman_commands() {
|
||||
"repo-triggers:run triggers on empty build result as configured by settings"
|
||||
"repo-update:check for packages updates and run build process if requested"
|
||||
"report:generate repository report according to current settings"
|
||||
"run:run multiple commands on success run of the previous command"
|
||||
"search:search for package in AUR using API"
|
||||
"service-clean:remove local caches"
|
||||
"service-config:dump configuration for the specified architecture"
|
||||
"service-config-validate:validate configuration and print found errors"
|
||||
"service-key-import:import PGP key from public sources to the repository user"
|
||||
"service-repositories:list all available repositories"
|
||||
"service-run:run multiple commands on success run of the previous command"
|
||||
"service-setup:create initial service configuration, requires root"
|
||||
"service-shell:drop into python shell"
|
||||
"service-tree-migrate:migrate repository tree between versions"
|
||||
@ -81,14 +78,14 @@ _shtab_ahriman_commands() {
|
||||
|
||||
_shtab_ahriman_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-a,--architecture}"[filter by target architecture (default\: None)]:architecture:"
|
||||
"*"{-a,--architecture}"[target architectures. For several subcommands it can be used multiple times (default\: None)]:architecture:"
|
||||
{-c,--configuration}"[configuration path (default\: \/etc\/ahriman.ini)]:configuration:"
|
||||
"--force[force run, remove file lock (default\: False)]"
|
||||
{-l,--lock}"[lock file (default\: \/tmp\/ahriman.lock)]:lock:"
|
||||
"--log-handler[explicit log handler specification. If none set, the handler will be guessed from environment (default\: None)]:log_handler:(console syslog journald)"
|
||||
{-q,--quiet}"[force disable any logging (default\: False)]"
|
||||
{--report,--no-report}"[force enable or disable reporting to web service (default\: True)]:report:"
|
||||
{-r,--repository}"[filter by target repository (default\: None)]:repository:"
|
||||
"*"{-r,--repository}"[target repository. For several subcommands it can be used multiple times (default\: None)]:repository:"
|
||||
"--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable (default\: False)]"
|
||||
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
|
||||
"(- : *)"{-V,--version}"[show program\'s version number and exit]"
|
||||
@ -101,9 +98,8 @@ _shtab_ahriman_add_options=(
|
||||
{--increment,--no-increment}"[increment package release (pkgrel) version on duplicate (default\: True)]:increment:"
|
||||
{-n,--now}"[run update function after (default\: False)]"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
||||
{-s,--source}"[explicitly specify the package source for this command (default\: auto)]:source:(auto archive aur directory local remote repository)"
|
||||
{-s,--source}"[explicitly specify the package source for this command (default\: PackageSource.Auto)]:source:(auto archive aur directory local remote repository)"
|
||||
{-u,--username}"[build as user (default\: None)]:username:"
|
||||
"*"{-v,--variable}"[apply specified makepkg variables to the next build (default\: None)]:variable:"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
@ -134,10 +130,7 @@ _shtab_ahriman_clean_options=(
|
||||
|
||||
_shtab_ahriman_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
{--secure,--no-secure}"[hide passwords and secrets from output (default\: True)]:secure:"
|
||||
":filter settings by section (default\: None):"
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_config_validate_options=(
|
||||
@ -204,9 +197,8 @@ _shtab_ahriman_package_add_options=(
|
||||
{--increment,--no-increment}"[increment package release (pkgrel) version on duplicate (default\: True)]:increment:"
|
||||
{-n,--now}"[run update function after (default\: False)]"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
||||
{-s,--source}"[explicitly specify the package source for this command (default\: auto)]:source:(auto archive aur directory local remote repository)"
|
||||
{-s,--source}"[explicitly specify the package source for this command (default\: PackageSource.Auto)]:source:(auto archive aur directory local remote repository)"
|
||||
{-u,--username}"[build as user (default\: None)]:username:"
|
||||
"*"{-v,--variable}"[apply specified makepkg variables to the next build (default\: None)]:variable:"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
@ -231,7 +223,7 @@ _shtab_ahriman_package_status_remove_options=(
|
||||
|
||||
_shtab_ahriman_package_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
|
||||
{-s,--status}"[new package build status (default\: BuildStatusEnum.Success)]:status:(unknown pending building failed success)"
|
||||
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
|
||||
)
|
||||
|
||||
@ -242,9 +234,8 @@ _shtab_ahriman_package_update_options=(
|
||||
{--increment,--no-increment}"[increment package release (pkgrel) version on duplicate (default\: True)]:increment:"
|
||||
{-n,--now}"[run update function after (default\: False)]"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
||||
{-s,--source}"[explicitly specify the package source for this command (default\: auto)]:source:(auto archive aur directory local remote repository)"
|
||||
{-s,--source}"[explicitly specify the package source for this command (default\: PackageSource.Auto)]:source:(auto archive aur directory local remote repository)"
|
||||
{-u,--username}"[build as user (default\: None)]:username:"
|
||||
"*"{-v,--variable}"[apply specified makepkg variables to the next build (default\: None)]:variable:"
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
@ -319,10 +310,7 @@ _shtab_ahriman_repo_clean_options=(
|
||||
|
||||
_shtab_ahriman_repo_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
{--secure,--no-secure}"[hide passwords and secrets from output (default\: True)]:secure:"
|
||||
":filter settings by section (default\: None):"
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_config_validate_options=(
|
||||
@ -414,7 +402,7 @@ _shtab_ahriman_repo_sign_options=(
|
||||
|
||||
_shtab_ahriman_repo_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new status (default\: success)]:status:(unknown pending building failed success)"
|
||||
{-s,--status}"[new status (default\: BuildStatusEnum.Success)]:status:(unknown pending building failed success)"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_sync_options=(
|
||||
@ -450,11 +438,6 @@ _shtab_ahriman_report_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
_shtab_ahriman_run_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):command to be run (quoted) without \`\`ahriman\`\`:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_search_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@ -474,10 +457,7 @@ _shtab_ahriman_service_clean_options=(
|
||||
|
||||
_shtab_ahriman_service_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
{--secure,--no-secure}"[hide passwords and secrets from output (default\: True)]:secure:"
|
||||
":filter settings by section (default\: None):"
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_service_config_validate_options=(
|
||||
@ -491,16 +471,6 @@ _shtab_ahriman_service_key_import_options=(
|
||||
":PGP key to import from public server:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_service_repositories_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--id-only,--no-id-only}"[show machine readable identifier instead (default\: False)]:id_only:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_service_run_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):command to be run (quoted) without \`\`ahriman\`\`:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_service_setup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
|
||||
@ -563,7 +533,7 @@ _shtab_ahriman_status_options=(
|
||||
|
||||
_shtab_ahriman_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
|
||||
{-s,--status}"[new package build status (default\: BuildStatusEnum.Success)]:status:(unknown pending building failed success)"
|
||||
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
|
||||
)
|
||||
|
||||
@ -591,7 +561,7 @@ _shtab_ahriman_user_add_options=(
|
||||
"--key[optional PGP key used by this user. The private key must be imported (default\: None)]:key:"
|
||||
"--packager[optional packager id used for build process in form of \`Name Surname \<mail\@example.com\>\` (default\: None)]:packager:"
|
||||
{-p,--password}"[user password. Blank password will be treated as empty password, which is in particular must be used for OAuth2 authorization type. (default\: None)]:password:"
|
||||
{-R,--role}"[user access level (default\: read)]:role:(unauthorized read reporter full)"
|
||||
{-R,--role}"[user access level (default\: UserAccess.Read)]:role:(unauthorized read reporter full)"
|
||||
":username for web service:"
|
||||
)
|
||||
|
||||
@ -677,14 +647,11 @@ _shtab_ahriman() {
|
||||
repo-triggers) _arguments -C -s $_shtab_ahriman_repo_triggers_options ;;
|
||||
repo-update) _arguments -C -s $_shtab_ahriman_repo_update_options ;;
|
||||
report) _arguments -C -s $_shtab_ahriman_report_options ;;
|
||||
run) _arguments -C -s $_shtab_ahriman_run_options ;;
|
||||
search) _arguments -C -s $_shtab_ahriman_search_options ;;
|
||||
service-clean) _arguments -C -s $_shtab_ahriman_service_clean_options ;;
|
||||
service-config) _arguments -C -s $_shtab_ahriman_service_config_options ;;
|
||||
service-config-validate) _arguments -C -s $_shtab_ahriman_service_config_validate_options ;;
|
||||
service-key-import) _arguments -C -s $_shtab_ahriman_service_key_import_options ;;
|
||||
service-repositories) _arguments -C -s $_shtab_ahriman_service_repositories_options ;;
|
||||
service-run) _arguments -C -s $_shtab_ahriman_service_run_options ;;
|
||||
service-setup) _arguments -C -s $_shtab_ahriman_service_setup_options ;;
|
||||
service-shell) _arguments -C -s $_shtab_ahriman_service_shell_options ;;
|
||||
service-tree-migrate) _arguments -C -s $_shtab_ahriman_service_tree_migrate_options ;;
|
||||
|
||||
@ -1,207 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from astroid import nodes
|
||||
from collections.abc import Iterable
|
||||
from enum import StrEnum
|
||||
from pylint.checkers import BaseRawFileChecker
|
||||
from pylint.lint import PyLinter
|
||||
from typing import Any
|
||||
|
||||
|
||||
class MethodTypeOrder(StrEnum):
|
||||
"""
|
||||
method type enumeration
|
||||
|
||||
Attributes:
|
||||
Class(MethodTypeOrder): (class attribute) class method
|
||||
Init(MethodTypeOrder): (class attribute) initialization method
|
||||
Magic(MethodTypeOrder): (class attribute) other magical methods
|
||||
New(MethodTypeOrder): (class attribute) constructor method
|
||||
Normal(MethodTypeOrder): (class attribute) usual method
|
||||
Property(MethodTypeOrder): (class attribute) property method
|
||||
Static(MethodTypeOrder): (class attribute) static method
|
||||
"""
|
||||
|
||||
Class = "classmethod"
|
||||
Init = "init"
|
||||
Magic = "magic"
|
||||
New = "new"
|
||||
Normal = "regular"
|
||||
Property = "property"
|
||||
Static = "staticmethod"
|
||||
|
||||
|
||||
class DefinitionOrder(BaseRawFileChecker):
|
||||
"""
|
||||
check if methods are defined in recommended order
|
||||
|
||||
Attributes:
|
||||
DECORATED_METHODS_ORDER(list[str]): (class attribute) predefined list of known function decorators
|
||||
"""
|
||||
|
||||
DECORATED_METHODS_ORDER = {
|
||||
"cached_property": MethodTypeOrder.Property,
|
||||
"classmethod": MethodTypeOrder.Class,
|
||||
"property": MethodTypeOrder.Property,
|
||||
"staticmethod": MethodTypeOrder.Static,
|
||||
}
|
||||
|
||||
name = "method-ordering"
|
||||
msgs = {
|
||||
"W6001": (
|
||||
"Invalid method order %s, expected %s",
|
||||
"methods-out-of-order",
|
||||
"Methods are defined out of recommended order.",
|
||||
)
|
||||
}
|
||||
options = (
|
||||
(
|
||||
"method-type-order",
|
||||
{
|
||||
"default": [
|
||||
"new",
|
||||
"init",
|
||||
"property",
|
||||
"classmethod",
|
||||
"staticmethod",
|
||||
"regular",
|
||||
"magic",
|
||||
],
|
||||
"type": "csv",
|
||||
"metavar": "<comma-separated types>",
|
||||
"help": "Method types order to check.",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def methods(source: Iterable[Any], start_lineno: int = 0) -> list[nodes.FunctionDef]:
|
||||
"""
|
||||
extract function nodes from list of raw nodes
|
||||
|
||||
Args:
|
||||
source(Iterable[Any]): all available nodes
|
||||
start_lineno(int, optional): minimal allowed line number (Default value = 0)
|
||||
|
||||
Returns:
|
||||
list[nodes.FunctionDef]: list of function nodes
|
||||
"""
|
||||
def is_defined_function(function: Any) -> bool:
|
||||
return isinstance(function, nodes.FunctionDef) \
|
||||
and function.lineno is not None \
|
||||
and function.lineno >= start_lineno
|
||||
|
||||
return list(filter(is_defined_function, source))
|
||||
|
||||
@staticmethod
|
||||
def resolve_type(function: nodes.FunctionDef) -> MethodTypeOrder:
|
||||
"""
|
||||
resolve type of the function
|
||||
|
||||
Args:
|
||||
function(nodes.FunctionDef): function definition
|
||||
|
||||
Returns:
|
||||
MethodTypeOrder: resolved function type
|
||||
"""
|
||||
# init methods
|
||||
if function.name in ("__new__",):
|
||||
return MethodTypeOrder.New
|
||||
if function.name in ("__init__", "__post_init__"):
|
||||
return MethodTypeOrder.Init
|
||||
|
||||
# decorated methods
|
||||
decorators = []
|
||||
if function.decorators is not None:
|
||||
decorators = [getattr(decorator, "name", None) for decorator in function.decorators.get_children()]
|
||||
for decorator in decorators:
|
||||
if decorator in DefinitionOrder.DECORATED_METHODS_ORDER:
|
||||
return DefinitionOrder.DECORATED_METHODS_ORDER[decorator]
|
||||
|
||||
# magic methods
|
||||
if function.name.startswith("__") and function.name.endswith("__"):
|
||||
return MethodTypeOrder.Magic
|
||||
|
||||
# normal method
|
||||
return MethodTypeOrder.Normal
|
||||
|
||||
def check_class(self, clazz: nodes.ClassDef) -> None:
|
||||
"""
|
||||
check class functions ordering
|
||||
|
||||
Args:
|
||||
clazz(nodes.ClassDef): class definition
|
||||
"""
|
||||
methods = self.methods(clazz.values(), clazz.lineno)
|
||||
self.check_functions(methods)
|
||||
|
||||
def check_functions(self, functions: list[nodes.FunctionDef]) -> None:
|
||||
"""
|
||||
check global functions ordering
|
||||
|
||||
Args:
|
||||
functions(list[nodes.FunctionDef]): list of functions in their defined order
|
||||
"""
|
||||
for real, expected in zip(functions, sorted(functions, key=self.comparator)):
|
||||
if real == expected:
|
||||
continue
|
||||
self.add_message("methods-out-of-order", line=real.lineno, args=(real.name, expected.name))
|
||||
break
|
||||
|
||||
def comparator(self, function: nodes.FunctionDef) -> tuple[int, str]:
|
||||
"""
|
||||
compare key for sorting function
|
||||
|
||||
Args:
|
||||
function(nodes.FunctionDef): function definition
|
||||
|
||||
Returns:
|
||||
tuple[int, str]: comparison key for the specified function definition
|
||||
"""
|
||||
function_type = self.resolve_type(function)
|
||||
try:
|
||||
function_type_index = self.linter.config.method_type_order.index(function_type)
|
||||
except ValueError:
|
||||
function_type_index = 10 # not in the list
|
||||
|
||||
return function_type_index, function.name
|
||||
|
||||
def process_module(self, node: nodes.Module) -> None:
|
||||
"""
|
||||
process module
|
||||
|
||||
Args:
|
||||
node(nodes.Module): module node to check
|
||||
"""
|
||||
# check global methods
|
||||
self.check_functions(self.methods(node.values()))
|
||||
# check class definitions
|
||||
for clazz in filter(lambda method: isinstance(method, nodes.ClassDef), node.values()):
|
||||
self.check_class(clazz)
|
||||
|
||||
|
||||
def register(linter: PyLinter) -> None:
|
||||
"""
|
||||
register custom checker
|
||||
|
||||
Args:
|
||||
linter(PyLinter): linter in which checker should be registered
|
||||
"""
|
||||
linter.register_checker(DefinitionOrder(linter))
|
||||
@ -40,7 +40,7 @@ check = [
|
||||
"mypy",
|
||||
"pylint",
|
||||
]
|
||||
docs = [
|
||||
docs = [
|
||||
"Sphinx",
|
||||
"argparse-manpage",
|
||||
"pydeps",
|
||||
|
||||
@ -17,4 +17,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2.12.0"
|
||||
__version__ = "2.11.0"
|
||||
|
||||
@ -69,7 +69,8 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH linux ReposItory MANager",
|
||||
epilog="Argument list can also be read from file by using @ prefix.",
|
||||
fromfile_prefix_chars="@", formatter_class=_formatter)
|
||||
parser.add_argument("-a", "--architecture", help="filter by target architecture")
|
||||
parser.add_argument("-a", "--architecture", help="target architectures. For several subcommands it can be used "
|
||||
"multiple times", action="append")
|
||||
parser.add_argument("-c", "--configuration", help="configuration path", type=Path,
|
||||
default=Path("/etc") / "ahriman.ini")
|
||||
parser.add_argument("--force", help="force run, remove file lock", action="store_true")
|
||||
@ -81,7 +82,8 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
|
||||
parser.add_argument("--report", help="force enable or disable reporting to web service",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-r", "--repository", help="filter by target repository")
|
||||
parser.add_argument("-r", "--repository", help="target repository. For several subcommands it can be used "
|
||||
"multiple times", action="append")
|
||||
# special secret argument for systemd unit. The issue is that systemd doesn't allow multiple arguments to template
|
||||
# name. This parameter accepts [[arch]-repo] in order to keep backward compatibility
|
||||
parser.add_argument("--repository-id", help=argparse.SUPPRESS)
|
||||
@ -96,8 +98,8 @@ def _parser() -> argparse.ArgumentParser:
|
||||
subparsers = parser.add_subparsers(title="command", help="command to run", dest="command")
|
||||
|
||||
_set_aur_search_parser(subparsers)
|
||||
_set_help_commands_unsafe_parser(subparsers)
|
||||
_set_help_parser(subparsers)
|
||||
_set_help_commands_unsafe_parser(subparsers)
|
||||
_set_help_updates_parser(subparsers)
|
||||
_set_help_version_parser(subparsers)
|
||||
_set_package_add_parser(subparsers)
|
||||
@ -128,8 +130,6 @@ def _parser() -> argparse.ArgumentParser:
|
||||
_set_service_config_parser(subparsers)
|
||||
_set_service_config_validate_parser(subparsers)
|
||||
_set_service_key_import_parser(subparsers)
|
||||
_set_service_repositories(subparsers)
|
||||
_set_service_run(subparsers)
|
||||
_set_service_setup_parser(subparsers)
|
||||
_set_service_shell_parser(subparsers)
|
||||
_set_service_tree_migrate_parser(subparsers)
|
||||
@ -161,27 +161,8 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("--sort-by", help="sort field by this field. In case if two packages have the same value of "
|
||||
"the specified field, they will be always sorted by name",
|
||||
default="name", choices=sorted(handlers.Search.SORT_FIELDS))
|
||||
parser.set_defaults(handler=handlers.Search, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for listing unsafe commands
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
|
||||
description="list unsafe commands as defined in default args", formatter_class=_formatter)
|
||||
parser.add_argument("command", help="instead of showing commands, just test command line for unsafe subcommand "
|
||||
"and return 0 in case if command is safe and 1 otherwise", nargs="*")
|
||||
parser.set_defaults(handler=handlers.UnsafeCommands, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True, parser=_parser)
|
||||
parser.set_defaults(handler=handlers.Search, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -199,11 +180,30 @@ def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="show help message for application or command and exit",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("command", help="show help message for specific command", nargs="?")
|
||||
parser.set_defaults(handler=handlers.Help, architecture="", lock=None, quiet=True, report=False, repository="",
|
||||
parser.set_defaults(handler=handlers.Help, architecture=[""], lock=None, quiet=True, report=False, repository=[""],
|
||||
unsafe=True, parser=_parser)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for listing unsafe commands
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
|
||||
description="list unsafe commands as defined in default args", formatter_class=_formatter)
|
||||
parser.add_argument("command", help="instead of showing commands, just test command line for unsafe subcommand "
|
||||
"and return 0 in case if command is safe and 1 otherwise", nargs="*")
|
||||
parser.set_defaults(handler=handlers.UnsafeCommands, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True, parser=_parser)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_help_updates_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for service update check subcommand
|
||||
@ -218,8 +218,8 @@ def _set_help_updates_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="request AUR for current version and compare with current service version",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit code if updates available", action="store_true")
|
||||
parser.set_defaults(handler=handlers.ServiceUpdates, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True)
|
||||
parser.set_defaults(handler=handlers.ServiceUpdates, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -235,8 +235,8 @@ def _set_help_version_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
parser = root.add_parser("help-version", aliases=["version"], help="application version",
|
||||
description="print application and its dependencies versions", formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Versions, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Versions, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -276,7 +276,6 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
||||
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
|
||||
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build", action="append")
|
||||
parser.set_defaults(handler=handlers.Add)
|
||||
return parser
|
||||
|
||||
@ -388,8 +387,8 @@ def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"it must end with ()")
|
||||
parser.add_argument("patch", help="path to file which contains function or variable value. If not set, "
|
||||
"the value will be read from stdin", type=Path, nargs="?")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture="", lock=None, report=False,
|
||||
repository="")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture=[""], lock=None, report=False,
|
||||
repository=[""])
|
||||
return parser
|
||||
|
||||
|
||||
@ -409,8 +408,8 @@ def _set_patch_list_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("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
|
||||
action="append")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture="", lock=None, report=False,
|
||||
repository="", unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -431,8 +430,8 @@ def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"to remove only specified PKGBUILD variables. In case if not set, "
|
||||
"it will remove all patches related to the package",
|
||||
action="append")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Remove, architecture="", lock=None, report=False,
|
||||
repository="")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Remove, architecture=[""], lock=None, report=False,
|
||||
repository=[""])
|
||||
return parser
|
||||
|
||||
|
||||
@ -456,8 +455,8 @@ def _set_patch_set_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("package", help="path to directory with changed files for patch addition/update", type=Path)
|
||||
parser.add_argument("-t", "--track", help="files which has to be tracked", action="append",
|
||||
default=["*.diff", "*.patch"])
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture="", lock=None, report=False,
|
||||
repository="", variable=None)
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture=[""], lock=None, report=False,
|
||||
repository=[""], variable=None)
|
||||
return parser
|
||||
|
||||
|
||||
@ -474,7 +473,7 @@ def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser = root.add_parser("repo-backup", help="backup repository data",
|
||||
description="backup repository settings and database", formatter_class=_formatter)
|
||||
parser.add_argument("path", help="path of the output archive", type=Path)
|
||||
parser.set_defaults(handler=handlers.Backup, architecture="", lock=None, report=False, repository="",
|
||||
parser.set_defaults(handler=handlers.Backup, architecture=[""], lock=None, report=False, repository=[""],
|
||||
unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -654,7 +653,7 @@ def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="restore settings and database", formatter_class=_formatter)
|
||||
parser.add_argument("path", help="path of the input archive", type=Path)
|
||||
parser.add_argument("-o", "--output", help="root path of the extracted files", type=Path, default=Path("/"))
|
||||
parser.set_defaults(handler=handlers.Restore, architecture="", lock=None, report=False, repository="",
|
||||
parser.set_defaults(handler=handlers.Restore, architecture=[""], lock=None, report=False, repository=[""],
|
||||
unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -831,10 +830,6 @@ def _set_service_config_parser(root: SubParserAction) -> argparse.ArgumentParser
|
||||
parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration",
|
||||
description="dump configuration for the specified architecture",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("section", help="filter settings by section", nargs="?")
|
||||
parser.add_argument("key", help="filter settings by key", nargs="?")
|
||||
parser.add_argument("--info", help="show additional information, e.g. configuration files",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--secure", help="hide passwords and secrets from output",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.set_defaults(handler=handlers.Dump, lock=None, quiet=True, report=False, unsafe=True)
|
||||
@ -880,48 +875,7 @@ def _set_service_key_import_parser(root: SubParserAction) -> argparse.ArgumentPa
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("--key-server", help="key server for key import", default="keyserver.ubuntu.com")
|
||||
parser.add_argument("key", help="PGP key to import from public server")
|
||||
parser.set_defaults(handler=handlers.KeyImport, architecture="", lock=None, report=False, repository="")
|
||||
return parser
|
||||
|
||||
|
||||
def _set_service_repositories(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repositories listing
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("service-repositories", help="show repositories",
|
||||
description="list all available repositories",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("--id-only", help="show machine readable identifier instead",
|
||||
action=argparse.BooleanOptionalAction, default=False)
|
||||
parser.set_defaults(handler=handlers.Repositories, architecture="", lock=None, report=False, repository="",
|
||||
unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_service_run(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for multicommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("service-run", aliases=["run"], help="run multiple commands",
|
||||
description="run multiple commands on success run of the previous command",
|
||||
epilog="Commands must be quoted by using usual bash rules. Processes will be spawned "
|
||||
"under the same user as this command",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("command", help="command to be run (quoted) without ``ahriman``", nargs="+")
|
||||
parser.set_defaults(handler=handlers.Run, architecture="", lock=None, report=False, repository="",
|
||||
unsafe=True, parser=_parser)
|
||||
parser.set_defaults(handler=handlers.KeyImport, architecture=[""], lock=None, report=False, repository=[""])
|
||||
return parser
|
||||
|
||||
|
||||
@ -1017,8 +971,8 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"which is in particular must be used for OAuth2 authorization type.")
|
||||
parser.add_argument("-R", "--role", help="user access level",
|
||||
type=UserAccess, choices=enum_values(UserAccess), default=UserAccess.Read)
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture="", lock=None, quiet=True,
|
||||
report=False, repository="")
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, quiet=True,
|
||||
report=False, repository=[""])
|
||||
return parser
|
||||
|
||||
|
||||
@ -1038,8 +992,8 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("username", help="filter users by username", nargs="?")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("-R", "--role", help="filter users by role", type=UserAccess, choices=enum_values(UserAccess))
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture="", lock=None, quiet=True,
|
||||
report=False, repository="", unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, quiet=True,
|
||||
report=False, repository=[""], unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -1057,8 +1011,8 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="remove user from the user mapping and update the configuration",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("username", help="username for web service")
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture="", lock=None, quiet=True,
|
||||
report=False, repository="")
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, quiet=True,
|
||||
report=False, repository=[""])
|
||||
return parser
|
||||
|
||||
|
||||
@ -1073,8 +1027,8 @@ def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("web", help="web server", description="start web server", formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Web, architecture="", lock=Path(tempfile.gettempdir()) / "ahriman-web.lock",
|
||||
report=False, repository="", parser=_parser)
|
||||
parser.set_defaults(handler=handlers.Web, lock=Path(tempfile.gettempdir()) / "ahriman-web.lock", report=False,
|
||||
parser=_parser)
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@ -32,8 +32,8 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
base application class
|
||||
|
||||
Examples:
|
||||
This class groups :class:`ahriman.core.repository.repository.Repository` methods into specific method which
|
||||
process all supposed actions caused by underlying action. E.g.::
|
||||
This class groups ``Repository`` methods into specific method which process all supposed actions caused by
|
||||
underlying action. E.g.::
|
||||
|
||||
>>> from ahriman.core.configuration import Configuration
|
||||
>>> from ahriman.models.package_source import PackageSource
|
||||
@ -49,8 +49,8 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
>>> # updates for specified packages
|
||||
>>> application.update(updates)
|
||||
|
||||
In case if specific actions or their order are required, the direct access to
|
||||
:class:`ahriman.core.repository.repository.Repository` must be used instead.
|
||||
In case if specific actions or their order are required, the direct access to ``Repository`` must
|
||||
be used instead.
|
||||
"""
|
||||
|
||||
def _known_packages(self) -> set[str]:
|
||||
@ -105,7 +105,8 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
tree = Tree.resolve(packages)
|
||||
for level in tree:
|
||||
for package in level:
|
||||
UpdatePrinter(package, local_versions.get(package.base))(verbose=True, log_fn=log_fn, separator=" -> ")
|
||||
UpdatePrinter(package, local_versions.get(package.base)).print(
|
||||
verbose=True, log_fn=log_fn, separator=" -> ")
|
||||
|
||||
def with_dependencies(self, packages: list[Package], *, process_dependencies: bool) -> list[Package]:
|
||||
"""
|
||||
@ -117,7 +118,7 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
|
||||
Returns:
|
||||
list[Package]: updated packages list. Packager for dependencies will be copied from
|
||||
original package
|
||||
original package
|
||||
"""
|
||||
def missing_dependencies(source: Iterable[Package]) -> dict[str, str | None]:
|
||||
# append list of known packages with packages which are in current sources
|
||||
@ -146,7 +147,7 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
# there is local cache, load package from it
|
||||
package = Package.from_build(source_dir, self.repository.architecture, username)
|
||||
else:
|
||||
package = Package.from_aur(package_name, username)
|
||||
package = Package.from_aur(package_name, self.repository.pacman, username)
|
||||
with_dependencies[package.base] = package
|
||||
|
||||
# register package in local database
|
||||
|
||||
@ -63,7 +63,7 @@ class ApplicationPackages(ApplicationProperties):
|
||||
source(str): package base name
|
||||
username(str | None): optional override of username for build process
|
||||
"""
|
||||
package = Package.from_aur(source, username)
|
||||
package = Package.from_aur(source, self.repository.pacman, username)
|
||||
self.database.build_queue_insert(package)
|
||||
self.database.remote_update(package)
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
packages: list[str] = []
|
||||
for single in probe.packages:
|
||||
try:
|
||||
_ = Package.from_aur(single, None)
|
||||
_ = Package.from_aur(single, self.repository.pacman, None)
|
||||
except Exception:
|
||||
packages.append(single)
|
||||
return packages
|
||||
|
||||
@ -30,9 +30,7 @@ from ahriman.application.handlers.patch import Patch
|
||||
from ahriman.application.handlers.rebuild import Rebuild
|
||||
from ahriman.application.handlers.remove import Remove
|
||||
from ahriman.application.handlers.remove_unknown import RemoveUnknown
|
||||
from ahriman.application.handlers.repositories import Repositories
|
||||
from ahriman.application.handlers.restore import Restore
|
||||
from ahriman.application.handlers.run import Run
|
||||
from ahriman.application.handlers.search import Search
|
||||
from ahriman.application.handlers.service_updates import ServiceUpdates
|
||||
from ahriman.application.handlers.setup import Setup
|
||||
|
||||
@ -23,7 +23,6 @@ from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
@ -46,12 +45,7 @@ class Add(Handler):
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
|
||||
application.on_start()
|
||||
|
||||
application.add(args.package, args.source, args.username)
|
||||
patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else []
|
||||
for package in args.package: # for each requested package insert patch
|
||||
application.database.patches_insert(package, patches)
|
||||
|
||||
if not args.now:
|
||||
return
|
||||
|
||||
|
||||
@ -44,18 +44,12 @@ class Dump(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
if args.info:
|
||||
root, _ = configuration.check_loaded()
|
||||
ConfigurationPathsPrinter(root, configuration.includes)(verbose=True, separator=" = ")
|
||||
root, _ = configuration.check_loaded()
|
||||
ConfigurationPathsPrinter(root, configuration.includes).print(verbose=True, separator=" = ")
|
||||
|
||||
match (args.section, args.key):
|
||||
case None, None: # full configuration
|
||||
dump = configuration.dump()
|
||||
for section, values in sorted(dump.items()):
|
||||
ConfigurationPrinter(section, values)(verbose=not args.secure, separator=" = ")
|
||||
case section, None: # section only
|
||||
values = dict(configuration.items(section)) if configuration.has_section(section) else {}
|
||||
ConfigurationPrinter(section, values)(verbose=not args.secure, separator=" = ")
|
||||
case section, key: # key only
|
||||
value = configuration.get(section, key, fallback="")
|
||||
StringPrinter(value)(verbose=False)
|
||||
# empty line
|
||||
StringPrinter("").print(verbose=False)
|
||||
|
||||
dump = configuration.dump()
|
||||
for section, values in sorted(dump.items()):
|
||||
ConfigurationPrinter(section, values).print(verbose=not args.secure, separator=" = ")
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from collections.abc import Iterable
|
||||
from multiprocessing import Pool
|
||||
|
||||
from ahriman.application.lock import Lock
|
||||
@ -39,8 +38,8 @@ class Handler:
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN(bool): (class attribute) allow running with multiple architectures
|
||||
|
||||
Examples:
|
||||
Wrapper for all command line actions, though each derived class implements :func:`run()` method, it usually
|
||||
must not be called directly. The recommended way is to call :func:`execute()` class method, e.g.::
|
||||
Wrapper for all command line actions, though each derived class implements ``run`` method, it usually must not
|
||||
be called directly. The recommended way is to call ``execute`` class method, e.g.::
|
||||
|
||||
>>> from ahriman.application.handlers import Add
|
||||
>>>
|
||||
@ -65,7 +64,7 @@ class Handler:
|
||||
configuration = Configuration.from_path(args.configuration, repository_id)
|
||||
|
||||
log_handler = LogLoader.handler(args.log_handler)
|
||||
LogLoader.load(repository_id, configuration, log_handler, quiet=args.quiet, report=args.report)
|
||||
LogLoader.load(configuration, log_handler, quiet=args.quiet, report=args.report)
|
||||
|
||||
with Lock(args, repository_id, configuration):
|
||||
cls.run(args, repository_id, configuration, report=args.report)
|
||||
@ -106,6 +105,59 @@ class Handler:
|
||||
|
||||
return 0 if all(result) else 1
|
||||
|
||||
@classmethod
|
||||
def repositories_extract(cls, args: argparse.Namespace) -> list[RepositoryId]:
|
||||
"""
|
||||
get known architectures
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
|
||||
Returns:
|
||||
list[RepositoryId]: list of repository names and architectures for which tree is created
|
||||
|
||||
Raises:
|
||||
MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed
|
||||
"""
|
||||
configuration = Configuration()
|
||||
configuration.load(args.configuration)
|
||||
# pylint, wtf???
|
||||
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
||||
|
||||
# preparse systemd repository-id argument
|
||||
# we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes
|
||||
if args.repository_id is not None:
|
||||
# repository parts is optional for backward compatibility
|
||||
architecture, *repository_parts = args.repository_id.split("/")
|
||||
args.architecture = [architecture]
|
||||
if repository_parts:
|
||||
args.repository = ["-".join(repository_parts)] # replace slash with dash
|
||||
|
||||
# extract repository names first
|
||||
names = args.repository
|
||||
if names is None: # try to read file system first
|
||||
names = RepositoryPaths.known_repositories(root)
|
||||
if not names: # try to read configuration now
|
||||
names = [configuration.get("repository", "name")]
|
||||
|
||||
# extract architecture names
|
||||
if (architectures := args.architecture) is not None:
|
||||
repositories = set(
|
||||
RepositoryId(architecture, name)
|
||||
for name in names
|
||||
for architecture in architectures
|
||||
)
|
||||
else: # try to read from file system
|
||||
repositories = set(
|
||||
RepositoryId(architecture, name)
|
||||
for name in names
|
||||
for architecture in RepositoryPaths.known_architectures(root, name)
|
||||
)
|
||||
|
||||
if not repositories:
|
||||
raise MissingArchitectureError(args.command)
|
||||
return sorted(repositories)
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
report: bool) -> None:
|
||||
@ -137,57 +189,3 @@ class Handler:
|
||||
"""
|
||||
if enabled and predicate:
|
||||
raise ExitCode
|
||||
|
||||
@staticmethod
|
||||
def repositories_extract(args: argparse.Namespace) -> list[RepositoryId]:
|
||||
"""
|
||||
get known architectures
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
|
||||
Returns:
|
||||
list[RepositoryId]: list of repository names and architectures for which tree is created
|
||||
|
||||
Raises:
|
||||
MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed
|
||||
"""
|
||||
configuration = Configuration()
|
||||
configuration.load(args.configuration)
|
||||
# pylint, wtf???
|
||||
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
||||
|
||||
# preparse systemd repository-id argument
|
||||
# we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes
|
||||
if args.repository_id is not None:
|
||||
separator = "/" if "/" in args.repository_id else "-" # systemd and non-systemd identifiers
|
||||
# repository parts is optional for backward compatibility
|
||||
architecture, *repository_parts = args.repository_id.split(separator) # maxsplit isn't used intentionally
|
||||
args.architecture = architecture
|
||||
if repository_parts:
|
||||
args.repository = "-".join(repository_parts) # replace slash with dash
|
||||
|
||||
# extract repository names first
|
||||
if (from_args := args.repository) is not None:
|
||||
repositories: Iterable[str] = [from_args]
|
||||
elif (from_filesystem := RepositoryPaths.known_repositories(root)):
|
||||
repositories = from_filesystem
|
||||
else: # try to read configuration now
|
||||
repositories = [configuration.get("repository", "name")]
|
||||
|
||||
# extract architecture names
|
||||
if (architecture := args.architecture) is not None:
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
)
|
||||
else: # try to read from file system
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
for architecture in RepositoryPaths.known_architectures(root, repository)
|
||||
)
|
||||
|
||||
if not parsed:
|
||||
raise MissingArchitectureError(args.command)
|
||||
return sorted(parsed)
|
||||
|
||||
@ -115,7 +115,7 @@ class Patch(Handler):
|
||||
package_base(str): package base
|
||||
patch(PkgbuildPatch): patch descriptor
|
||||
"""
|
||||
application.database.patches_insert(package_base, [patch])
|
||||
application.database.patches_insert(package_base, patch)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_list(application: Application, package_base: str | None, variables: list[str] | None,
|
||||
@ -133,7 +133,7 @@ class Patch(Handler):
|
||||
Patch.check_if_empty(exit_code, not patches)
|
||||
|
||||
for base, patch in patches.items():
|
||||
PatchPrinter(base, patch)(verbose=True, separator=" = ")
|
||||
PatchPrinter(base, patch).print(verbose=True, separator=" = ")
|
||||
|
||||
@staticmethod
|
||||
def patch_set_remove(application: Application, package_base: str, variables: list[str] | None) -> None:
|
||||
|
||||
@ -49,7 +49,7 @@ class RemoveUnknown(Handler):
|
||||
|
||||
if args.dry_run:
|
||||
for package in sorted(unknown_packages):
|
||||
StringPrinter(package)(verbose=False)
|
||||
StringPrinter(package).print(verbose=False)
|
||||
return
|
||||
|
||||
application.remove(unknown_packages)
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import RepositoryPrinter
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class Repositories(Handler):
|
||||
"""
|
||||
repositories listing handler
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
dummy_args = argparse.Namespace(
|
||||
architecture=None,
|
||||
configuration=args.configuration,
|
||||
repository=None,
|
||||
repository_id=None,
|
||||
)
|
||||
for repository in cls.repositories_extract(dummy_args):
|
||||
RepositoryPrinter(repository)(verbose=not args.id_only)
|
||||
@ -1,66 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import shlex
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class Run(Handler):
|
||||
"""
|
||||
multicommand handler
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
parser = args.parser()
|
||||
for command in args.command:
|
||||
status = Run.run_command(shlex.split(command), parser)
|
||||
Run.check_if_empty(True, not status)
|
||||
|
||||
@staticmethod
|
||||
def run_command(command: list[str], parser: argparse.ArgumentParser) -> bool:
|
||||
"""
|
||||
run command specified by the argument
|
||||
|
||||
Args:
|
||||
command(list[str]): command to run
|
||||
parser(argparse.ArgumentParser): generated argument parser
|
||||
|
||||
Returns:
|
||||
bool: status of the command
|
||||
"""
|
||||
args = parser.parse_args(command)
|
||||
handler: Handler = args.handler
|
||||
return handler.execute(args) == 0
|
||||
@ -22,6 +22,7 @@ import argparse
|
||||
from dataclasses import fields
|
||||
from collections.abc import Callable, Iterable
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.alpm.remote import AUR, Official
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -58,14 +59,16 @@ class Search(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
official_packages_list = Official.multisearch(*args.search)
|
||||
aur_packages_list = AUR.multisearch(*args.search)
|
||||
application = Application(repository_id, configuration, report=report)
|
||||
|
||||
official_packages_list = Official.multisearch(*args.search, pacman=application.repository.pacman)
|
||||
aur_packages_list = AUR.multisearch(*args.search, pacman=application.repository.pacman)
|
||||
Search.check_if_empty(args.exit_code, not official_packages_list and not aur_packages_list)
|
||||
|
||||
for packages_list in (official_packages_list, aur_packages_list):
|
||||
# keep sorting by packages source
|
||||
for package in Search.sort(packages_list, args.sort_by):
|
||||
AurPrinter(package)(verbose=args.info)
|
||||
AurPrinter(package).print(verbose=args.info)
|
||||
|
||||
@staticmethod
|
||||
def sort(packages: Iterable[AURPackage], sort_by: str) -> list[AURPackage]:
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
import argparse
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import UpdatePrinter
|
||||
@ -46,7 +47,9 @@ class ServiceUpdates(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
remote = Package.from_aur("ahriman", None)
|
||||
application = Application(repository_id, configuration, report=report)
|
||||
|
||||
remote = Package.from_aur("ahriman", application.repository.pacman, None)
|
||||
_, release = remote.version.rsplit("-", 1) # we don't store pkgrel locally, so we just append it
|
||||
local_version = f"{__version__}-{release}"
|
||||
|
||||
@ -56,5 +59,5 @@ class ServiceUpdates(Handler):
|
||||
if same_version:
|
||||
return
|
||||
|
||||
UpdatePrinter(remote, local_version)(verbose=True, separator=" -> ")
|
||||
UpdatePrinter(remote, local_version).print(verbose=True, separator=" -> ")
|
||||
ServiceUpdates.check_if_empty(args.exit_code, not same_version)
|
||||
|
||||
@ -126,16 +126,16 @@ class Setup(Handler):
|
||||
configuration.set_option(section, "target", " ".join([target.name.lower() for target in sign_targets]))
|
||||
configuration.set_option(section, "key", args.sign_key)
|
||||
|
||||
section = Configuration.section_name("web", repository_id.name, repository_id.architecture)
|
||||
if args.web_port is not None:
|
||||
configuration.set_option("web", "port", str(args.web_port))
|
||||
configuration.set_option(section, "port", str(args.web_port))
|
||||
if args.web_unix_socket is not None:
|
||||
configuration.set_option("web", "unix_socket", str(args.web_unix_socket))
|
||||
configuration.set_option(section, "unix_socket", str(args.web_unix_socket))
|
||||
|
||||
if args.generate_salt:
|
||||
configuration.set_option("auth", "salt", User.generate_password(20))
|
||||
|
||||
(root.include / "00-setup-overrides.ini").unlink(missing_ok=True) # remove old-style configuration
|
||||
target = root.include / f"00-setup-overrides-{repository_id.id}.ini"
|
||||
target = root.include / "00-setup-overrides.ini"
|
||||
with target.open("w") as ahriman_configuration:
|
||||
configuration.write(ahriman_configuration)
|
||||
|
||||
@ -160,7 +160,7 @@ class Setup(Handler):
|
||||
configuration = Configuration(allow_no_value=True)
|
||||
# preserve case
|
||||
# stupid mypy thinks that it is impossible
|
||||
configuration.optionxform = lambda optionstr: optionstr # type: ignore[method-assign]
|
||||
configuration.optionxform = lambda key: key # type: ignore[method-assign]
|
||||
|
||||
# load default configuration first
|
||||
# we cannot use Include here because it will be copied to new chroot, thus no includes there
|
||||
|
||||
@ -51,7 +51,7 @@ class Shell(Handler):
|
||||
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"))(verbose=False)
|
||||
StringPrinter(path.read_text(encoding="utf8")).print(verbose=False)
|
||||
|
||||
local_variables = {
|
||||
"architecture": repository_id.architecture,
|
||||
|
||||
@ -53,7 +53,7 @@ class Status(Handler):
|
||||
client = Application(repository_id, configuration, report=True).repository.reporter
|
||||
if args.ahriman:
|
||||
service_status = client.status_get()
|
||||
StatusPrinter(service_status.status)(verbose=args.info)
|
||||
StatusPrinter(service_status.status).print(verbose=args.info)
|
||||
if args.package:
|
||||
packages: list[tuple[Package, BuildStatus]] = sum(
|
||||
(client.package_get(base) for base in args.package),
|
||||
@ -67,4 +67,4 @@ class Status(Handler):
|
||||
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\
|
||||
lambda item: args.status is None or item[1].status == args.status
|
||||
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
|
||||
PackagePrinter(package, package_status)(verbose=args.info)
|
||||
PackagePrinter(package, package_status).print(verbose=args.info)
|
||||
|
||||
@ -45,18 +45,14 @@ class StatusUpdate(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=True)
|
||||
client = application.repository.reporter
|
||||
# we are using reporter here
|
||||
client = Application(repository_id, configuration, report=True).repository.reporter
|
||||
|
||||
match args.action:
|
||||
case Action.Update if args.package:
|
||||
# update packages statuses
|
||||
packages = application.repository.packages()
|
||||
for base in args.package:
|
||||
if (local := next((package for package in packages if package.base == base), None)) is not None:
|
||||
client.package_add(local, args.status)
|
||||
else:
|
||||
client.package_update(base, args.status)
|
||||
for package in args.package:
|
||||
client.package_update(package, args.status)
|
||||
case Action.Update:
|
||||
# update service status
|
||||
client.status_update(args.status)
|
||||
|
||||
@ -50,11 +50,11 @@ class Structure(Handler):
|
||||
partitions = Tree.partition(application.repository.packages(), count=args.partitions)
|
||||
|
||||
for partition_id, partition in enumerate(partitions):
|
||||
StringPrinter(f"partition #{partition_id}")(verbose=False)
|
||||
StringPrinter(f"partition #{partition_id}").print(verbose=False)
|
||||
|
||||
tree = Tree.resolve(partition)
|
||||
for num, level in enumerate(tree):
|
||||
TreePrinter(num, level)(verbose=True, separator=" ")
|
||||
TreePrinter(num, level).print(verbose=True, separator=" ")
|
||||
|
||||
# empty line
|
||||
StringPrinter("")(verbose=False)
|
||||
StringPrinter("").print(verbose=False)
|
||||
|
||||
@ -50,7 +50,7 @@ class UnsafeCommands(Handler):
|
||||
UnsafeCommands.check_unsafe(args.command, unsafe_commands, parser)
|
||||
else:
|
||||
for command in unsafe_commands:
|
||||
StringPrinter(command)(verbose=True)
|
||||
StringPrinter(command).print(verbose=True)
|
||||
|
||||
@staticmethod
|
||||
def check_unsafe(command: list[str], unsafe_commands: list[str], parser: argparse.ArgumentParser) -> None:
|
||||
|
||||
@ -61,7 +61,7 @@ class Users(Handler):
|
||||
users = database.user_list(args.username, args.role)
|
||||
Users.check_if_empty(args.exit_code, not users)
|
||||
for user in users:
|
||||
UserPrinter(user)(verbose=True)
|
||||
UserPrinter(user).print(verbose=True)
|
||||
case Action.Remove:
|
||||
database.user_remove(args.username)
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ class Validate(Handler):
|
||||
if validator.validate(configuration.dump()):
|
||||
return # no errors found
|
||||
for node, errors in validator.errors.items():
|
||||
ValidationPrinter(node, errors)(verbose=True)
|
||||
ValidationPrinter(node, errors).print(verbose=True)
|
||||
|
||||
# as we reach this part it means that we always have errors
|
||||
Validate.check_if_empty(args.exit_code, True)
|
||||
|
||||
@ -55,9 +55,9 @@ class Versions(Handler):
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
VersionPrinter(f"Module version {__version__}",
|
||||
{"Python": sys.version})(verbose=False, separator=" ")
|
||||
{"Python": sys.version}).print(verbose=False, separator=" ")
|
||||
packages = Versions.package_dependencies("ahriman")
|
||||
VersionPrinter("Installed packages", dict(packages))(verbose=False, separator=" ")
|
||||
VersionPrinter("Installed packages", dict(packages)).print(verbose=False, separator=" ")
|
||||
|
||||
@staticmethod
|
||||
def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]:
|
||||
@ -67,8 +67,8 @@ class Versions(Handler):
|
||||
Args:
|
||||
root(str): root package name
|
||||
|
||||
Yields:
|
||||
tuple[str, str]: map of installed dependency to its version
|
||||
Returns:
|
||||
Generator[tuple[str, str], None, None]: map of installed dependency to its version
|
||||
"""
|
||||
def dependencies_by_key(key: str) -> Generator[str, None, None]:
|
||||
# in importlib it returns requires in the following format
|
||||
|
||||
@ -32,7 +32,7 @@ class Web(Handler):
|
||||
web server handler
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
@ -47,20 +47,13 @@ class Web(Handler):
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
# we are using local import for optional dependencies
|
||||
from ahriman.web.web import run_server, setup_server
|
||||
from ahriman.web.web import run_server, setup_service
|
||||
|
||||
spawner_args = Web.extract_arguments(args, configuration)
|
||||
spawner = Spawn(args.parser(), list(spawner_args))
|
||||
spawner_args = Web.extract_arguments(args, repository_id, configuration)
|
||||
spawner = Spawn(args.parser(), repository_id, list(spawner_args))
|
||||
spawner.start()
|
||||
|
||||
dummy_args = argparse.Namespace(
|
||||
architecture=None,
|
||||
configuration=args.configuration,
|
||||
repository=None,
|
||||
repository_id=None,
|
||||
)
|
||||
repositories = cls.repositories_extract(dummy_args)
|
||||
application = setup_server(configuration, spawner, repositories)
|
||||
application = setup_service(repository_id, configuration, spawner)
|
||||
run_server(application)
|
||||
|
||||
# terminate spawn process at the last
|
||||
@ -68,17 +61,22 @@ class Web(Handler):
|
||||
spawner.join()
|
||||
|
||||
@staticmethod
|
||||
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]:
|
||||
def extract_arguments(args: argparse.Namespace, repository_id: RepositoryId,
|
||||
configuration: Configuration) -> Generator[str, None, None]:
|
||||
"""
|
||||
extract list of arguments used for current command, except for command specific ones
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
|
||||
Yields:
|
||||
str: command line arguments which were used for this specific command
|
||||
Returns:
|
||||
Generator[str, None, None]: command line arguments which were used for this specific command
|
||||
"""
|
||||
# read architecture from the same argument list
|
||||
yield from ["--architecture", repository_id.architecture]
|
||||
yield from ["--repository", repository_id.name]
|
||||
# read configuration path from current settings
|
||||
if (configuration_path := configuration.path) is not None:
|
||||
yield from ["--configuration", str(configuration_path)]
|
||||
|
||||
@ -70,22 +70,16 @@ class Lock(LazyLogging):
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
lock_suffix = f"{repository_id.name}_{repository_id.architecture}"
|
||||
self.path: Path | None = \
|
||||
args.lock.with_stem(f"{args.lock.stem}_{repository_id.id}") if args.lock is not None else None
|
||||
args.lock.with_stem(f"{args.lock.stem}_{lock_suffix}") if args.lock is not None else None
|
||||
|
||||
self.force: bool = args.force
|
||||
self.unsafe: bool = args.unsafe
|
||||
self.wait_timeout: int = args.wait_timeout
|
||||
|
||||
self.paths = configuration.repository_paths
|
||||
self.reporter = Client.load(repository_id, configuration, report=args.report)
|
||||
|
||||
def check_user(self) -> None:
|
||||
"""
|
||||
check if current user is actually owner of ahriman root
|
||||
"""
|
||||
check_user(self.paths, unsafe=self.unsafe)
|
||||
self.paths.tree_create()
|
||||
self.reporter = Client.load(configuration, report=args.report)
|
||||
|
||||
def check_version(self) -> None:
|
||||
"""
|
||||
@ -96,6 +90,13 @@ class Lock(LazyLogging):
|
||||
self.logger.warning("status watcher version mismatch, our %s, their %s",
|
||||
__version__, status.version)
|
||||
|
||||
def check_user(self) -> None:
|
||||
"""
|
||||
check if current user is actually owner of ahriman root
|
||||
"""
|
||||
check_user(self.paths, unsafe=self.unsafe)
|
||||
self.paths.tree_create()
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
remove lock file
|
||||
|
||||
@ -22,7 +22,7 @@ import shutil
|
||||
from collections.abc import Callable, Generator
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore[import-not-found]
|
||||
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore[import]
|
||||
from string import Template
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
@ -113,13 +113,13 @@ class AUR(Remote):
|
||||
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
|
||||
return self.parse_response(response.json())
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -133,13 +133,13 @@ class AUR(Remote):
|
||||
except StopIteration:
|
||||
raise UnknownPackageError(package_name) from None
|
||||
|
||||
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
|
||||
def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): keywords to search
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
|
||||
@ -107,13 +107,13 @@ class Official(Remote):
|
||||
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
|
||||
return self.parse_response(response.json())
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -127,13 +127,13 @@ class Official(Remote):
|
||||
except StopIteration:
|
||||
raise UnknownPackageError(package_name) from None
|
||||
|
||||
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
|
||||
def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): keywords to search
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
|
||||
@ -38,13 +38,13 @@ class OfficialSyncdb(Official):
|
||||
Still we leave search function based on the official repositories RPC.
|
||||
"""
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -52,9 +52,6 @@ class OfficialSyncdb(Official):
|
||||
Raises:
|
||||
UnknownPackageError: package doesn't exist
|
||||
"""
|
||||
if pacman is None:
|
||||
raise UnknownPackageError(package_name)
|
||||
|
||||
try:
|
||||
return next(AURPackage.from_pacman(package) for package in pacman.package_get(package_name))
|
||||
except StopIteration:
|
||||
|
||||
@ -28,27 +28,25 @@ class Remote(SyncHttpClient):
|
||||
|
||||
Examples:
|
||||
These classes are designed to be used without instancing. In order to achieve it several class methods are
|
||||
provided: :func:`info()`, :func:`multisearch()` and :func:`search()`. Thus, the basic flow is the following::
|
||||
provided: ``info``, ``multisearch`` and ``search``. Thus, the basic flow is the following::
|
||||
|
||||
>>> from ahriman.core.alpm.remote import AUR, Official
|
||||
>>>
|
||||
>>> package = AUR.info("ahriman", pacman=pacman)
|
||||
>>> search_result = Official.multisearch("pacman", "manager", pacman=pacman)
|
||||
|
||||
Differnece between :func:`search()` and :func:`multisearch()` is that :func:`search()` passes all arguments to
|
||||
underlying wrapper directly, whereas :func:`multisearch()` splits search one by one and finds intersection
|
||||
between search results.
|
||||
Differnece between ``search`` and ``multisearch`` is that ``search`` passes all arguments to underlying wrapper
|
||||
directly, whereas ``multisearch`` splits search one by one and finds intersection between search results.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def info(cls, package_name: str, *, pacman: Pacman | None = None) -> AURPackage:
|
||||
def info(cls, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
|
||||
(Default value = None)
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -56,15 +54,14 @@ class Remote(SyncHttpClient):
|
||||
return cls().package_info(package_name, pacman=pacman)
|
||||
|
||||
@classmethod
|
||||
def multisearch(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]:
|
||||
def multisearch(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
"""
|
||||
search in remote repository by using API with multiple words. This method is required in order to handle
|
||||
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
|
||||
|
||||
Args:
|
||||
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
|
||||
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
|
||||
(Default value = None)
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages each of them matches all search terms
|
||||
@ -114,27 +111,26 @@ class Remote(SyncHttpClient):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def search(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]:
|
||||
def search(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
|
||||
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
|
||||
(Default value = None)
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
"""
|
||||
return cls().package_search(*keywords, pacman=pacman)
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -144,13 +140,13 @@ class Remote(SyncHttpClient):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
|
||||
def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): keywords to search
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
|
||||
@ -36,6 +36,8 @@ class Repo(LazyLogging):
|
||||
uid(int): uid of the repository owner user
|
||||
"""
|
||||
|
||||
_check_output = check_output
|
||||
|
||||
def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
@ -67,7 +69,7 @@ class Repo(LazyLogging):
|
||||
Args:
|
||||
path(Path): path to archive to add
|
||||
"""
|
||||
check_output(
|
||||
Repo._check_output(
|
||||
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path),
|
||||
exception=BuildError.from_process(path.name),
|
||||
cwd=self.paths.repository,
|
||||
@ -78,8 +80,8 @@ class Repo(LazyLogging):
|
||||
"""
|
||||
create empty repository database
|
||||
"""
|
||||
check_output("repo-add", *self.sign_args, str(self.repo_path),
|
||||
cwd=self.paths.repository, logger=self.logger, user=self.uid)
|
||||
Repo._check_output("repo-add", *self.sign_args, str(self.repo_path),
|
||||
cwd=self.paths.repository, logger=self.logger, user=self.uid)
|
||||
|
||||
def remove(self, package: str, filename: Path) -> None:
|
||||
"""
|
||||
@ -94,7 +96,7 @@ class Repo(LazyLogging):
|
||||
full_path.unlink()
|
||||
|
||||
# remove package from registry
|
||||
check_output(
|
||||
Repo._check_output(
|
||||
"repo-remove", *self.sign_args, str(self.repo_path), package,
|
||||
exception=BuildError.from_process(package),
|
||||
cwd=self.paths.repository,
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
import aiohttp_security # type: ignore[import-untyped]
|
||||
import aiohttp_security # type: ignore[import]
|
||||
_has_aiohttp_security = True
|
||||
except ImportError:
|
||||
_has_aiohttp_security = False
|
||||
|
||||
@ -34,7 +34,6 @@ class OAuth(Mapping):
|
||||
Attributes:
|
||||
client_id(str): application client id
|
||||
client_secret(str): application client secret key
|
||||
icon(str): icon to be used in login control
|
||||
provider(aioauth_client.OAuth2Client): provider class, should be one of aiohttp-client provided classes
|
||||
redirect_uri(str): redirect URI registered in provider
|
||||
scopes(str): list of scopes required by the application
|
||||
@ -59,7 +58,6 @@ class OAuth(Mapping):
|
||||
self.provider = self.get_provider(configuration.get("auth", "oauth_provider"))
|
||||
# it is list, but we will have to convert to string it anyway
|
||||
self.scopes = configuration.get("auth", "oauth_scopes")
|
||||
self.icon = configuration.get("auth", "oauth_icon", fallback="google")
|
||||
|
||||
@property
|
||||
def auth_control(self) -> str:
|
||||
@ -69,7 +67,7 @@ class OAuth(Mapping):
|
||||
Returns:
|
||||
str: login control as html code to insert
|
||||
"""
|
||||
return f"""<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-{self.icon}"></i> login</a>"""
|
||||
return """<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-google"></i> login</a>"""
|
||||
|
||||
@staticmethod
|
||||
def get_provider(name: str) -> type[aioauth_client.OAuth2Client]:
|
||||
|
||||
@ -42,6 +42,8 @@ class Sources(LazyLogging):
|
||||
DEFAULT_BRANCH = "master" # default fallback branch
|
||||
DEFAULT_COMMIT_AUTHOR = ("ahriman", "ahriman@localhost")
|
||||
|
||||
_check_output = check_output
|
||||
|
||||
@staticmethod
|
||||
def extend_architectures(sources_dir: Path, architecture: str) -> list[PkgbuildPatch]:
|
||||
"""
|
||||
@ -80,20 +82,20 @@ class Sources(LazyLogging):
|
||||
branch = remote.branch or instance.DEFAULT_BRANCH
|
||||
if is_initialized_git:
|
||||
instance.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
|
||||
check_output("git", "fetch", "--quiet", "--depth", "1", "origin", branch,
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
Sources._check_output("git", "fetch", "--quiet", "origin", branch,
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
elif remote.git_url is not None:
|
||||
instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
|
||||
check_output("git", "clone", "--quiet", "--depth", "1", "--branch", branch, "--single-branch",
|
||||
remote.git_url, str(sources_dir), cwd=sources_dir.parent, logger=instance.logger)
|
||||
Sources._check_output("git", "clone", "--quiet", "--branch", branch, "--single-branch",
|
||||
remote.git_url, str(sources_dir), cwd=sources_dir.parent, logger=instance.logger)
|
||||
else:
|
||||
# it will cause an exception later
|
||||
instance.logger.error("%s is not initialized, but no remote provided", sources_dir)
|
||||
|
||||
# and now force reset to our branch
|
||||
check_output("git", "checkout", "--force", branch, cwd=sources_dir, logger=instance.logger)
|
||||
check_output("git", "reset", "--quiet", "--hard", f"origin/{branch}",
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
Sources._check_output("git", "checkout", "--force", branch, cwd=sources_dir, logger=instance.logger)
|
||||
Sources._check_output("git", "reset", "--quiet", "--hard", f"origin/{branch}",
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
# move content if required
|
||||
# we are using full path to source directory in order to make append possible
|
||||
@ -112,7 +114,7 @@ class Sources(LazyLogging):
|
||||
bool: True in case if there is any remote and false otherwise
|
||||
"""
|
||||
instance = Sources()
|
||||
remotes = check_output("git", "remote", cwd=sources_dir, logger=instance.logger)
|
||||
remotes = Sources._check_output("git", "remote", cwd=sources_dir, logger=instance.logger)
|
||||
return bool(remotes)
|
||||
|
||||
@staticmethod
|
||||
@ -126,8 +128,8 @@ class Sources(LazyLogging):
|
||||
instance = Sources()
|
||||
if not (sources_dir / ".git").is_dir():
|
||||
# skip initializing in case if it was already
|
||||
check_output("git", "init", "--quiet", "--initial-branch", instance.DEFAULT_BRANCH,
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
Sources._check_output("git", "init", "--quiet", "--initial-branch", instance.DEFAULT_BRANCH,
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
# extract local files...
|
||||
files = ["PKGBUILD", ".SRCINFO"] + [str(path) for path in Package.local_files(sources_dir)]
|
||||
@ -191,7 +193,7 @@ class Sources(LazyLogging):
|
||||
return # no changes to push, just skip action
|
||||
|
||||
git_url, branch = remote.git_source()
|
||||
check_output("git", "push", "--quiet", git_url, branch, cwd=sources_dir, logger=instance.logger)
|
||||
Sources._check_output("git", "push", "--quiet", git_url, branch, cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
def add(self, sources_dir: Path, *pattern: str, intent_to_add: bool = False) -> None:
|
||||
"""
|
||||
@ -212,8 +214,8 @@ class Sources(LazyLogging):
|
||||
self.logger.info("found matching files %s", found_files)
|
||||
# add them to index
|
||||
args = ["--intent-to-add"] if intent_to_add else []
|
||||
check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
|
||||
cwd=sources_dir, logger=self.logger)
|
||||
Sources._check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
|
||||
cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def commit(self, sources_dir: Path, message: str | None = None,
|
||||
commit_author: tuple[str, str] | None = None) -> bool:
|
||||
@ -243,7 +245,8 @@ class Sources(LazyLogging):
|
||||
environment["GIT_AUTHOR_NAME"] = environment["GIT_COMMITTER_NAME"] = user
|
||||
environment["GIT_AUTHOR_EMAIL"] = environment["GIT_COMMITTER_EMAIL"] = email
|
||||
|
||||
check_output("git", "commit", "--quiet", *args, cwd=sources_dir, logger=self.logger, environment=environment)
|
||||
Sources._check_output("git", "commit", "--quiet", *args,
|
||||
cwd=sources_dir, logger=self.logger, environment=environment)
|
||||
|
||||
return True
|
||||
|
||||
@ -257,7 +260,7 @@ class Sources(LazyLogging):
|
||||
Returns:
|
||||
str: patch as plain string
|
||||
"""
|
||||
return check_output("git", "diff", cwd=sources_dir, logger=self.logger)
|
||||
return Sources._check_output("git", "diff", cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def has_changes(self, sources_dir: Path) -> bool:
|
||||
"""
|
||||
@ -270,7 +273,7 @@ class Sources(LazyLogging):
|
||||
bool: True if there are uncommitted changes and False otherwise
|
||||
"""
|
||||
# there is --exit-code argument to diff, however, there might be other process errors
|
||||
changes = check_output("git", "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger)
|
||||
changes = Sources._check_output("git", "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger)
|
||||
return bool(changes)
|
||||
|
||||
def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None:
|
||||
@ -299,7 +302,7 @@ class Sources(LazyLogging):
|
||||
# create patch
|
||||
self.logger.info("apply patch %s from database at %s", patch.key, sources_dir)
|
||||
if patch.is_plain_diff:
|
||||
check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||
cwd=sources_dir, input_data=patch.serialize(), logger=self.logger)
|
||||
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||
cwd=sources_dir, input_data=patch.serialize(), logger=self.logger)
|
||||
else:
|
||||
patch.write(sources_dir / "PKGBUILD")
|
||||
|
||||
@ -45,6 +45,8 @@ class Task(LazyLogging):
|
||||
uid(int): uid of the repository owner user
|
||||
"""
|
||||
|
||||
_check_output = check_output
|
||||
|
||||
def __init__(self, package: Package, configuration: Configuration, architecture: str,
|
||||
paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
@ -66,13 +68,13 @@ class Task(LazyLogging):
|
||||
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
|
||||
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
|
||||
|
||||
def build(self, sources_dir: Path, **kwargs: str | None) -> list[Path]:
|
||||
def build(self, sources_dir: Path, packager: str | None = None) -> list[Path]:
|
||||
"""
|
||||
run package build
|
||||
|
||||
Args:
|
||||
sources_dir(Path): path to where sources are
|
||||
**kwargs(str | None): environment variables to be passed to build processes
|
||||
packager(str | None, optional): optional packager override (Default value = None)
|
||||
|
||||
Returns:
|
||||
list[Path]: paths of produced packages
|
||||
@ -83,29 +85,25 @@ class Task(LazyLogging):
|
||||
command.extend(["--"] + self.makepkg_flags)
|
||||
self.logger.info("using %s for %s", command, self.package.base)
|
||||
|
||||
environment: dict[str, str] = {
|
||||
key: value
|
||||
for key, value in kwargs.items()
|
||||
if value is not None
|
||||
}
|
||||
environment: dict[str, str] = {}
|
||||
if packager is not None:
|
||||
environment["PACKAGER"] = packager
|
||||
self.logger.info("using environment variables %s", environment)
|
||||
|
||||
check_output(
|
||||
Task._check_output(
|
||||
*command,
|
||||
exception=BuildError.from_process(self.package.base),
|
||||
cwd=sources_dir,
|
||||
logger=self.logger,
|
||||
user=self.uid,
|
||||
environment=environment,
|
||||
)
|
||||
environment=environment)
|
||||
|
||||
# well it is not actually correct, but we can deal with it
|
||||
packages = check_output(
|
||||
packages = Task._check_output(
|
||||
"makepkg", "--packagelist",
|
||||
exception=BuildError.from_process(self.package.base),
|
||||
cwd=sources_dir,
|
||||
logger=self.logger,
|
||||
environment=environment,
|
||||
logger=self.logger
|
||||
).splitlines()
|
||||
return [Path(package) for package in packages]
|
||||
|
||||
|
||||
@ -45,8 +45,8 @@ class Configuration(configparser.RawConfigParser):
|
||||
|
||||
Examples:
|
||||
Configuration class provides additional method in order to handle application configuration. Since this class is
|
||||
derived from built-in :class:`configparser.RawConfigParser` class, the same flow is applicable here.
|
||||
Nevertheless, it is recommended to use :func:`from_path` class method which also calls initialization methods::
|
||||
derived from built-in ``configparser.RawConfigParser`` class, the same flow is applicable here. Nevertheless,
|
||||
it is recommended to use ``from_path`` class method which also calls initialization methods::
|
||||
|
||||
>>> from pathlib import Path
|
||||
>>>
|
||||
@ -57,14 +57,13 @@ class Configuration(configparser.RawConfigParser):
|
||||
The configuration instance loaded in this way will contain only sections which are defined for the specified
|
||||
architecture according to the merge rules. Moreover, the architecture names will be removed from section names.
|
||||
|
||||
In order to get current settings, the :func:`check_loaded` method can be used. This method will raise an
|
||||
:exc:`ahriman.core.exceptions.InitializeError` in case if configuration was not yet loaded::
|
||||
In order to get current settings, the ``check_loaded`` method can be used. This method will raise an
|
||||
``InitializeError`` in case if configuration was not yet loaded::
|
||||
|
||||
>>> path, repository_id = configuration.check_loaded()
|
||||
"""
|
||||
|
||||
_LEGACY_ARCHITECTURE_SPECIFIC_SECTIONS = ["web"]
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS = ["alpm", "build", "sign"]
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS = ["alpm", "build", "sign", "web"]
|
||||
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
|
||||
converters: dict[str, Callable[[str], Any]] # typing guard
|
||||
|
||||
@ -73,7 +72,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
default constructor. In the most cases must not be called directly
|
||||
|
||||
Args:
|
||||
allow_no_value(bool, optional): copies :class:`configparser.RawConfigParser` behaviour. In case if it is set
|
||||
allow_no_value(bool, optional): copies ``configparser.RawConfigParser`` behaviour. In case if it is set
|
||||
to ``True``, the keys without values will be allowed (Default value = False)
|
||||
"""
|
||||
configparser.RawConfigParser.__init__(
|
||||
@ -83,7 +82,6 @@ class Configuration(configparser.RawConfigParser):
|
||||
converters={
|
||||
"list": shlex.split,
|
||||
"path": self._convert_path,
|
||||
"pathlist": lambda value: [self._convert_path(element) for element in shlex.split(value)],
|
||||
}
|
||||
)
|
||||
|
||||
@ -161,6 +159,25 @@ class Configuration(configparser.RawConfigParser):
|
||||
configuration.merge_sections(repository_id)
|
||||
return configuration
|
||||
|
||||
@staticmethod
|
||||
def override_sections(section: str, repository_id: RepositoryId) -> list[str]:
|
||||
"""
|
||||
extract override sections
|
||||
|
||||
Args:
|
||||
section(str): section name
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
list[str]: architecture and repository specific sections in correct order
|
||||
"""
|
||||
# the valid order is global < per architecture < per repository < per repository and architecture
|
||||
return [
|
||||
Configuration.section_name(section, repository_id.architecture), # architecture specific override
|
||||
Configuration.section_name(section, repository_id.name), # override with repository name
|
||||
Configuration.section_name(section, repository_id.name, repository_id.architecture), # both
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def section_name(section: str, *suffixes: str | None) -> str:
|
||||
"""
|
||||
@ -214,7 +231,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
dict[str, dict[str, str]]: configuration dump for specific architecture
|
||||
"""
|
||||
return {
|
||||
section: dict(self.items(section))
|
||||
section: dict(self[section])
|
||||
for section in self.sections()
|
||||
}
|
||||
|
||||
@ -224,8 +241,6 @@ class Configuration(configparser.RawConfigParser):
|
||||
|
||||
def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore[empty-body]
|
||||
|
||||
def getpathlist(self, *args: Any, **kwargs: Any) -> list[Path]: ... # type: ignore[empty-body]
|
||||
|
||||
def gettype(self, section: str, repository_id: RepositoryId, *, fallback: str | None = None) -> tuple[str, str]:
|
||||
"""
|
||||
get type variable with fallback to old logic. Despite the fact that it has same semantics as other get* methods,
|
||||
@ -264,27 +279,20 @@ class Configuration(configparser.RawConfigParser):
|
||||
if not path.is_file(): # fallback to the system file
|
||||
path = self.SYSTEM_CONFIGURATION_PATH
|
||||
self.path = path
|
||||
|
||||
self.read(self.path)
|
||||
self.load_includes() # load includes
|
||||
self.load_includes()
|
||||
|
||||
def load_includes(self, path: Path | None = None) -> None:
|
||||
def load_includes(self) -> None:
|
||||
"""
|
||||
load configuration includes from specified path
|
||||
|
||||
Args:
|
||||
path(Path | None, optional): path to directory with include files. If none set, the default path will be
|
||||
used (Default value = None)
|
||||
load configuration includes
|
||||
"""
|
||||
self.includes = [] # reset state
|
||||
|
||||
try:
|
||||
path = path or self.include
|
||||
for include in sorted(path.glob("*.ini")):
|
||||
if include == self.logging_path:
|
||||
for path in sorted(self.include.glob("*.ini")):
|
||||
if path == self.logging_path:
|
||||
continue # we don't want to load logging explicitly
|
||||
self.read(include)
|
||||
self.includes.append(include)
|
||||
self.read(path)
|
||||
self.includes.append(path)
|
||||
except (FileNotFoundError, configparser.NoOptionError, configparser.NoSectionError):
|
||||
pass
|
||||
|
||||
@ -297,7 +305,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
"""
|
||||
self.repository_id = repository_id
|
||||
|
||||
for section in self.ARCHITECTURE_SPECIFIC_SECTIONS + self._LEGACY_ARCHITECTURE_SPECIFIC_SECTIONS:
|
||||
for section in self.ARCHITECTURE_SPECIFIC_SECTIONS:
|
||||
for specific in self.override_sections(section, repository_id):
|
||||
if self.has_section(specific):
|
||||
# if there is no such section it means that there is no overrides for this arch,
|
||||
@ -312,26 +320,6 @@ class Configuration(configparser.RawConfigParser):
|
||||
continue
|
||||
self.remove_section(foreign)
|
||||
|
||||
def override_sections(self, section: str, repository_id: RepositoryId) -> list[str]:
|
||||
"""
|
||||
extract override sections
|
||||
|
||||
Args:
|
||||
section(str): section name
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
list[str]: architecture and repository specific sections in correct order
|
||||
"""
|
||||
if repository_id.is_empty: # special case, guess sections from configuration
|
||||
return sorted(specific for specific in self.sections() if specific.startswith(f"{section}:"))
|
||||
# the valid order is global < per architecture < per repository < per repository and architecture
|
||||
return [
|
||||
Configuration.section_name(section, repository_id.architecture), # architecture specific override
|
||||
Configuration.section_name(section, repository_id.name), # override with repository name
|
||||
Configuration.section_name(section, repository_id.name, repository_id.architecture), # both
|
||||
]
|
||||
|
||||
def reload(self) -> None:
|
||||
"""
|
||||
reload configuration if possible or raise exception otherwise
|
||||
@ -344,7 +332,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
|
||||
def set_option(self, section: str, option: str, value: str) -> None:
|
||||
"""
|
||||
set option. Unlike default :func:`configparser.RawConfigParser.set` it also creates section if it does not exist
|
||||
set option. Unlike default ``configparser.RawConfigParser.set`` it also creates section if it does not exist
|
||||
|
||||
Args:
|
||||
section(str): section name
|
||||
|
||||
@ -42,15 +42,14 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"include": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
"path_type": "dir",
|
||||
},
|
||||
"logging": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
"path_type": "file",
|
||||
},
|
||||
"suppress_http_log_errors": {
|
||||
"type": "boolean",
|
||||
@ -69,16 +68,12 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"mirror": {
|
||||
"type": "string",
|
||||
"required": True,
|
||||
"empty": False,
|
||||
"is_url": [],
|
||||
},
|
||||
"repositories": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
"required": True,
|
||||
"empty": False,
|
||||
},
|
||||
@ -87,7 +82,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
"path_type": "dir",
|
||||
},
|
||||
"use_ahriman_cache": {
|
||||
"type": "boolean",
|
||||
@ -119,11 +113,9 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"cookie_secret_key": {
|
||||
"type": "string",
|
||||
@ -135,17 +127,11 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"coerce": "integer",
|
||||
"min": 0,
|
||||
},
|
||||
"oauth_icon": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"oauth_provider": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"oauth_scopes": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"salt": {
|
||||
"type": "string",
|
||||
@ -158,55 +144,36 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"archbuild_flags": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"build_command": {
|
||||
"type": "string",
|
||||
"required": True,
|
||||
"empty": False,
|
||||
},
|
||||
"ignore_packages": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"makepkg_flags": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"makechrootpkg_flags": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"triggers": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"triggers_known": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"vcs_allowed_age": {
|
||||
"type": "integer",
|
||||
@ -220,14 +187,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"schema": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"root": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"type": "string",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
"path_type": "dir",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -245,7 +208,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -254,7 +216,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"schema": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
"is_url": ["http", "https"],
|
||||
},
|
||||
"debug": {
|
||||
@ -268,10 +229,7 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"debug_allowed_hosts": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"enable_archive_upload": {
|
||||
"type": "boolean",
|
||||
@ -279,12 +237,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
"is_ip_address": ["localhost"],
|
||||
},
|
||||
"index_url": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
"is_url": ["http", "https"],
|
||||
},
|
||||
"max_body_size": {
|
||||
@ -294,7 +250,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
@ -307,18 +262,12 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
"path_type": "dir",
|
||||
},
|
||||
"templates": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"path_exists": True,
|
||||
"path_type": "dir",
|
||||
},
|
||||
"empty": False,
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
@ -335,7 +284,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"wait_timeout": {
|
||||
"type": "integer",
|
||||
|
||||
@ -46,6 +46,6 @@ class ShellInterpolator(configparser.Interpolation):
|
||||
"""
|
||||
# At the moment it seems that it is the most legit way to handle environment variables
|
||||
# Template behaviour is literally the same as shell
|
||||
# In addition, we are using shell-like variables in some cases (see :attr:`alpm.mirror` option),
|
||||
# thus we would like to keep them alive
|
||||
# In addition, we are using shell-like variables in some cases (see ``alpm.mirror`` option), thus we would like
|
||||
# to keep them alive
|
||||
return Template(value).safe_substitute(os.environ)
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
#
|
||||
import ipaddress
|
||||
|
||||
from cerberus import TypeDefinition, Validator as RootValidator # type: ignore[import-untyped]
|
||||
from cerberus import TypeDefinition, Validator as RootValidator # type: ignore[import]
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
@ -45,7 +45,7 @@ class Validator(RootValidator):
|
||||
Args:
|
||||
configuration(Configuration): configuration instance used for extraction
|
||||
*args(Any): positional arguments to be passed to base validator
|
||||
**kwargs(Any): keyword arguments to be passed to base validator
|
||||
**kwargs(): keyword arguments to be passed to base validator
|
||||
"""
|
||||
RootValidator.__init__(self, *args, **kwargs)
|
||||
self.configuration: Configuration = kwargs["configuration"]
|
||||
@ -162,21 +162,3 @@ class Validator(RootValidator):
|
||||
self._error(field, f"Path {value} must not exist")
|
||||
case False if constraint:
|
||||
self._error(field, f"Path {value} must exist")
|
||||
|
||||
def _validate_path_type(self, constraint: str, field: str, value: Path) -> None:
|
||||
"""
|
||||
check if paths is file, directory or whatever. The match will be performed as call of ``is_{constraint}``
|
||||
method of the path object
|
||||
|
||||
Args:
|
||||
constraint(str): path type to be matched
|
||||
field(str): field name to be checked
|
||||
value(Path): value to be checked
|
||||
|
||||
Examples:
|
||||
The rule's arguments are validated against this schema:
|
||||
{"type": "string"}
|
||||
"""
|
||||
fn = getattr(value, f"is_{constraint}")
|
||||
if not fn():
|
||||
self._error(field, f"Path {value} must be type of {constraint}")
|
||||
|
||||
@ -92,7 +92,6 @@ class Migrations(LazyLogging):
|
||||
list[Migration]: list of found migrations
|
||||
"""
|
||||
migrations: list[Migration] = []
|
||||
|
||||
package_dir = Path(__file__).resolve().parent
|
||||
modules = [module_name for (_, module_name, _) in iter_modules([str(package_dir)])]
|
||||
|
||||
|
||||
@ -21,7 +21,6 @@ from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.database.operations import Operations
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class BuildOperations(Operations):
|
||||
@ -29,16 +28,13 @@ class BuildOperations(Operations):
|
||||
operations for build queue functions
|
||||
"""
|
||||
|
||||
def build_queue_clear(self, package_base: str | None, repository_id: RepositoryId | None = None) -> None:
|
||||
def build_queue_clear(self, package_base: str | None) -> None:
|
||||
"""
|
||||
remove packages from build queue
|
||||
|
||||
Args:
|
||||
package_base(str | None): optional filter by package base
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
@ -47,44 +43,36 @@ class BuildOperations(Operations):
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def build_queue_get(self, repository_id: RepositoryId | None = None) -> list[Package]:
|
||||
def build_queue_get(self) -> list[Package]:
|
||||
"""
|
||||
retrieve packages from build queue
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Return:
|
||||
list[Package]: list of packages to be built
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> list[Package]:
|
||||
return [
|
||||
Package.from_json(row["properties"])
|
||||
for row in connection.execute(
|
||||
"""select properties from build_queue where repository = :repository""",
|
||||
{"repository": repository_id.id}
|
||||
{"repository": self.repository_id.id}
|
||||
)
|
||||
]
|
||||
|
||||
return self.with_connection(run)
|
||||
|
||||
def build_queue_insert(self, package: Package, repository_id: RepositoryId | None = None) -> None:
|
||||
def build_queue_insert(self, package: Package) -> None:
|
||||
"""
|
||||
insert packages to build queue
|
||||
|
||||
Args:
|
||||
package(Package): package to be inserted
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
@ -98,7 +86,7 @@ class BuildOperations(Operations):
|
||||
{
|
||||
"package_base": package.base,
|
||||
"properties": package.view(),
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
@ -21,7 +21,6 @@ from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.database.operations import Operations
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class LogsOperations(Operations):
|
||||
@ -29,8 +28,7 @@ class LogsOperations(Operations):
|
||||
logs operations
|
||||
"""
|
||||
|
||||
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0,
|
||||
repository_id: RepositoryId | None = None) -> list[tuple[float, str]]:
|
||||
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
|
||||
"""
|
||||
extract logs for specified package base
|
||||
|
||||
@ -38,13 +36,10 @@ class LogsOperations(Operations):
|
||||
package_base(str): package base to extract logs
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Return:
|
||||
list[tuple[float, str]]: sorted package log records and their timestamps
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> list[tuple[float, str]]:
|
||||
return [
|
||||
(row["created"], row["record"])
|
||||
@ -56,7 +51,7 @@ class LogsOperations(Operations):
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
@ -64,8 +59,7 @@ class LogsOperations(Operations):
|
||||
|
||||
return self.with_connection(run)
|
||||
|
||||
def logs_insert(self, log_record_id: LogRecordId, created: float, record: str,
|
||||
repository_id: RepositoryId | None = None) -> None:
|
||||
def logs_insert(self, log_record_id: LogRecordId, created: float, record: str) -> None:
|
||||
"""
|
||||
write new log record to database
|
||||
|
||||
@ -73,10 +67,7 @@ class LogsOperations(Operations):
|
||||
log_record_id(LogRecordId): current log record id
|
||||
created(float): log created timestamp from log record attribute
|
||||
record(str): log record
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
@ -90,23 +81,21 @@ class LogsOperations(Operations):
|
||||
"version": log_record_id.version,
|
||||
"created": created,
|
||||
"record": record,
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def logs_remove(self, package_base: str, version: str | None, repository_id: RepositoryId | None = None) -> None:
|
||||
def logs_remove(self, package_base: str, version: str | None) -> None:
|
||||
"""
|
||||
remove log records for the specified package
|
||||
|
||||
Args:
|
||||
package_base(str): package base to remove logs
|
||||
version(str | None): package version. If set it will remove only logs belonging to another version
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
version(str): package version. If set it will remove only logs belonging to another
|
||||
version
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
@ -118,7 +107,7 @@ class LogsOperations(Operations):
|
||||
{
|
||||
"package_base": package_base,
|
||||
"version": version,
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ class Operations(LazyLogging):
|
||||
|
||||
Attributes:
|
||||
path(Path): path to the database file
|
||||
repository_id(RepositoryId): repository unique identifier to perform implicit filtering
|
||||
"""
|
||||
|
||||
def __init__(self, path: Path, repository_id: RepositoryId) -> None:
|
||||
@ -47,7 +48,7 @@ class Operations(LazyLogging):
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
self.path = path
|
||||
self._repository_id = repository_id
|
||||
self.repository_id = repository_id
|
||||
|
||||
@staticmethod
|
||||
def factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> dict[str, Any]:
|
||||
|
||||
@ -25,7 +25,6 @@ from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class PackageOperations(Operations):
|
||||
@ -33,26 +32,23 @@ class PackageOperations(Operations):
|
||||
package operations
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _package_remove_package_base(connection: Connection, package_base: str, repository_id: RepositoryId) -> None:
|
||||
def _package_remove_package_base(self, connection: Connection, package_base: str) -> None:
|
||||
"""
|
||||
remove package base information
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
package_base(str): package base name
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
connection.execute(
|
||||
"""delete from package_statuses where package_base = :package_base and repository = :repository""",
|
||||
{"package_base": package_base, "repository": repository_id.id})
|
||||
{"package_base": package_base, "repository": self.repository_id.id})
|
||||
connection.execute(
|
||||
"""delete from package_bases where package_base = :package_base and repository = :repository""",
|
||||
{"package_base": package_base, "repository": repository_id.id})
|
||||
{"package_base": package_base, "repository": self.repository_id.id})
|
||||
|
||||
@staticmethod
|
||||
def _package_remove_packages(connection: Connection, package_base: str, current_packages: Iterable[str],
|
||||
repository_id: RepositoryId) -> None:
|
||||
def _package_remove_packages(self, connection: Connection, package_base: str,
|
||||
current_packages: Iterable[str]) -> None:
|
||||
"""
|
||||
remove packages belong to the package base
|
||||
|
||||
@ -60,7 +56,6 @@ class PackageOperations(Operations):
|
||||
connection(Connection): database connection
|
||||
package_base(str): package base name
|
||||
current_packages(Iterable[str]): current packages list which has to be left in database
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
packages = [
|
||||
package
|
||||
@ -68,22 +63,20 @@ class PackageOperations(Operations):
|
||||
"""
|
||||
select package, repository from packages
|
||||
where package_base = :package_base and repository = :repository""",
|
||||
{"package_base": package_base, "repository": repository_id.id})
|
||||
{"package_base": package_base, "repository": self.repository_id.id})
|
||||
if package["package"] not in current_packages
|
||||
]
|
||||
connection.executemany(
|
||||
"""delete from packages where package = :package and repository = :repository""",
|
||||
packages)
|
||||
|
||||
@staticmethod
|
||||
def _package_update_insert_base(connection: Connection, package: Package, repository_id: RepositoryId) -> None:
|
||||
def _package_update_insert_base(self, connection: Connection, package: Package) -> None:
|
||||
"""
|
||||
insert base package into table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
package(Package): package properties
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
connection.execute(
|
||||
"""
|
||||
@ -104,19 +97,17 @@ class PackageOperations(Operations):
|
||||
"web_url": package.remote.web_url,
|
||||
"source": package.remote.source.value,
|
||||
"packager": package.packager,
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _package_update_insert_packages(connection: Connection, package: Package, repository_id: RepositoryId) -> None:
|
||||
def _package_update_insert_packages(self, connection: Connection, package: Package) -> None:
|
||||
"""
|
||||
insert packages into table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
package(Package): package properties
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
package_list = []
|
||||
for name, description in package.packages.items():
|
||||
@ -125,7 +116,7 @@ class PackageOperations(Operations):
|
||||
package_list.append({
|
||||
"package": name,
|
||||
"package_base": package.base,
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
**description.view(),
|
||||
})
|
||||
connection.executemany(
|
||||
@ -150,9 +141,7 @@ class PackageOperations(Operations):
|
||||
""",
|
||||
package_list)
|
||||
|
||||
@staticmethod
|
||||
def _package_update_insert_status(connection: Connection, package_base: str, status: BuildStatus,
|
||||
repository_id: RepositoryId) -> None:
|
||||
def _package_update_insert_status(self, connection: Connection, package_base: str, status: BuildStatus) -> None:
|
||||
"""
|
||||
insert base package status into table
|
||||
|
||||
@ -160,7 +149,6 @@ class PackageOperations(Operations):
|
||||
connection(Connection): database connection
|
||||
package_base(str): package base name
|
||||
status(BuildStatus): new build status
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
connection.execute(
|
||||
"""
|
||||
@ -175,17 +163,15 @@ class PackageOperations(Operations):
|
||||
"package_base": package_base,
|
||||
"status": status.status.value,
|
||||
"last_updated": status.timestamp,
|
||||
"repository": repository_id.id,
|
||||
"repository": self.repository_id.id,
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def _packages_get_select_package_bases(connection: Connection, repository_id: RepositoryId) -> dict[str, Package]:
|
||||
def _packages_get_select_package_bases(self, connection: Connection) -> dict[str, Package]:
|
||||
"""
|
||||
select package bases from the table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
dict[str, Package]: map of the package base to its descriptor (without packages themselves)
|
||||
@ -199,41 +185,36 @@ class PackageOperations(Operations):
|
||||
packager=row["packager"] or None,
|
||||
) for row in connection.execute(
|
||||
"""select * from package_bases where repository = :repository""",
|
||||
{"repository": repository_id.id}
|
||||
{"repository": self.repository_id.id}
|
||||
)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _packages_get_select_packages(connection: Connection, packages: dict[str, Package],
|
||||
repository_id: RepositoryId) -> dict[str, Package]:
|
||||
def _packages_get_select_packages(self, connection: Connection, packages: dict[str, Package]) -> dict[str, Package]:
|
||||
"""
|
||||
select packages from the table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
packages(dict[str, Package]): packages descriptor map
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
dict[str, Package]: map of the package base to its descriptor including individual packages
|
||||
"""
|
||||
for row in connection.execute(
|
||||
"""select * from packages where repository = :repository""",
|
||||
{"repository": repository_id.id}
|
||||
{"repository": self.repository_id.id}
|
||||
):
|
||||
if row["package_base"] not in packages:
|
||||
continue # normally must never happen though
|
||||
packages[row["package_base"]].packages[row["package"]] = PackageDescription.from_json(row)
|
||||
return packages
|
||||
|
||||
@staticmethod
|
||||
def _packages_get_select_statuses(connection: Connection, repository_id: RepositoryId) -> dict[str, BuildStatus]:
|
||||
def _packages_get_select_statuses(self, connection: Connection) -> dict[str, BuildStatus]:
|
||||
"""
|
||||
select package build statuses from the table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
dict[str, BuildStatus]: map of the package base to its status
|
||||
@ -242,97 +223,74 @@ class PackageOperations(Operations):
|
||||
row["package_base"]: BuildStatus.from_json({"status": row["status"], "timestamp": row["last_updated"]})
|
||||
for row in connection.execute(
|
||||
"""select * from package_statuses where repository = :repository""",
|
||||
{"repository": repository_id.id}
|
||||
{"repository": self.repository_id.id}
|
||||
)
|
||||
}
|
||||
|
||||
def package_remove(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
||||
def package_remove(self, package_base: str) -> None:
|
||||
"""
|
||||
remove package from database
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
self._package_remove_packages(connection, package_base, [], repository_id)
|
||||
self._package_remove_package_base(connection, package_base, repository_id)
|
||||
self._package_remove_packages(connection, package_base, [])
|
||||
self._package_remove_package_base(connection, package_base)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def package_update(self, package: Package, status: BuildStatus, repository_id: RepositoryId | None = None) -> None:
|
||||
def package_update(self, package: Package, status: BuildStatus) -> None:
|
||||
"""
|
||||
update package status
|
||||
|
||||
Args:
|
||||
package(Package): package properties
|
||||
status(BuildStatus): new build status
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
self._package_update_insert_base(connection, package, repository_id)
|
||||
self._package_update_insert_status(connection, package.base, status, repository_id)
|
||||
self._package_update_insert_packages(connection, package, repository_id)
|
||||
self._package_remove_packages(connection, package.base, package.packages.keys(), repository_id)
|
||||
self._package_update_insert_base(connection, package)
|
||||
self._package_update_insert_status(connection, package.base, status)
|
||||
self._package_update_insert_packages(connection, package)
|
||||
self._package_remove_packages(connection, package.base, package.packages.keys())
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def packages_get(self, repository_id: RepositoryId | None = None) -> list[tuple[Package, BuildStatus]]:
|
||||
def packages_get(self) -> list[tuple[Package, BuildStatus]]:
|
||||
"""
|
||||
get package list and their build statuses from database
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Return:
|
||||
list[tuple[Package, BuildStatus]]: list of package properties and their statuses
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> Generator[tuple[Package, BuildStatus], None, None]:
|
||||
packages = self._packages_get_select_package_bases(connection, repository_id)
|
||||
statuses = self._packages_get_select_statuses(connection, repository_id)
|
||||
per_package_base = self._packages_get_select_packages(connection, packages, repository_id)
|
||||
for package_base, package in per_package_base.items():
|
||||
packages = self._packages_get_select_package_bases(connection)
|
||||
statuses = self._packages_get_select_statuses(connection)
|
||||
for package_base, package in self._packages_get_select_packages(connection, packages).items():
|
||||
yield package, statuses.get(package_base, BuildStatus())
|
||||
|
||||
return self.with_connection(lambda connection: list(run(connection)))
|
||||
|
||||
def remote_update(self, package: Package, repository_id: RepositoryId | None = None) -> None:
|
||||
def remote_update(self, package: Package) -> None:
|
||||
"""
|
||||
update package remote source
|
||||
|
||||
Args:
|
||||
package(Package): package properties
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
return self.with_connection(
|
||||
lambda connection: self._package_update_insert_base(connection, package),
|
||||
commit=True)
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
self._package_update_insert_base(connection, package, repository_id)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def remotes_get(self, repository_id: RepositoryId | None = None) -> dict[str, RemoteSource]:
|
||||
def remotes_get(self) -> dict[str, RemoteSource]:
|
||||
"""
|
||||
get packages remotes based on current settings
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Returns:
|
||||
dict[str, RemoteSource]: map of package base to its remote sources
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> dict[str, Package]:
|
||||
return self._packages_get_select_package_bases(connection, repository_id)
|
||||
|
||||
packages = self.with_connection(self._packages_get_select_package_bases)
|
||||
return {
|
||||
package_base: package.remote
|
||||
for package_base, package in self.with_connection(run).items()
|
||||
for package_base, package in packages.items()
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from collections import defaultdict
|
||||
|
||||
from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.database.operations import Operations
|
||||
@ -41,16 +42,16 @@ class PatchOperations(Operations):
|
||||
"""
|
||||
return self.patches_list(package_base, None).get(package_base, [])
|
||||
|
||||
def patches_insert(self, package_base: str, patches: list[PkgbuildPatch]) -> None:
|
||||
def patches_insert(self, package_base: str, patch: PkgbuildPatch) -> None:
|
||||
"""
|
||||
insert or update patch in database
|
||||
|
||||
Args:
|
||||
package_base(str): package base to insert
|
||||
patches(list[PkgbuildPatch]): patch content
|
||||
patch(PkgbuildPatch): patch content
|
||||
"""
|
||||
def run(connection: Connection) -> None:
|
||||
connection.executemany(
|
||||
connection.execute(
|
||||
"""
|
||||
insert into patches
|
||||
(package_base, variable, patch)
|
||||
@ -59,14 +60,7 @@ class PatchOperations(Operations):
|
||||
on conflict (package_base, coalesce(variable, '')) do update set
|
||||
patch = :patch
|
||||
""",
|
||||
[
|
||||
{
|
||||
"package_base": package_base,
|
||||
"variable": patch.key,
|
||||
"patch": patch.value,
|
||||
} for patch in patches
|
||||
]
|
||||
)
|
||||
{"package_base": package_base, "variable": patch.key, "patch": patch.value})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
@ -95,7 +89,7 @@ class PatchOperations(Operations):
|
||||
if variables is not None and patch.key not in variables:
|
||||
continue
|
||||
patches[package].append(patch)
|
||||
return patches
|
||||
return dict(patches)
|
||||
|
||||
def patches_remove(self, package_base: str, variables: list[str] | None) -> None:
|
||||
"""
|
||||
@ -108,21 +102,12 @@ class PatchOperations(Operations):
|
||||
def run_many(connection: Connection) -> None:
|
||||
patches = variables or [] # suppress mypy warning
|
||||
connection.executemany(
|
||||
"""
|
||||
delete from patches where package_base = :package_base and variable = :variable
|
||||
""",
|
||||
[
|
||||
{
|
||||
"package_base": package_base,
|
||||
"variable": variable,
|
||||
} for variable in patches
|
||||
])
|
||||
"""delete from patches where package_base = :package_base and variable = :variable""",
|
||||
[{"package_base": package_base, "variable": variable} for variable in patches])
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
delete from patches where package_base = :package_base
|
||||
""",
|
||||
"""delete from patches where package_base = :package_base""",
|
||||
{"package_base": package_base})
|
||||
|
||||
if variables is not None:
|
||||
|
||||
@ -35,7 +35,7 @@ class SQLite(AuthOperations, BuildOperations, LogsOperations, PackageOperations,
|
||||
|
||||
Examples:
|
||||
Database wrapper must be used together with migration and SQLite3 setup. In order to perform it there is
|
||||
:func:`load()` class method::
|
||||
``load`` class method::
|
||||
|
||||
>>> from ahriman.core.configuration import Configuration
|
||||
>>>
|
||||
|
||||
@ -62,7 +62,7 @@ class BuildError(RuntimeError):
|
||||
|
||||
class CalledProcessError(subprocess.CalledProcessError):
|
||||
"""
|
||||
like :exc:`subprocess.CalledProcessError`, but better
|
||||
like ``subprocess.CalledProcessError``, but better
|
||||
"""
|
||||
|
||||
def __init__(self, status_code: int, process: list[str], stderr: str) -> None:
|
||||
|
||||
@ -26,7 +26,6 @@ from ahriman.core.formatters.configuration_paths_printer import ConfigurationPat
|
||||
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
|
||||
from ahriman.core.formatters.package_printer import PackagePrinter
|
||||
from ahriman.core.formatters.patch_printer import PatchPrinter
|
||||
from ahriman.core.formatters.repository_printer import RepositoryPrinter
|
||||
from ahriman.core.formatters.status_printer import StatusPrinter
|
||||
from ahriman.core.formatters.tree_printer import TreePrinter
|
||||
from ahriman.core.formatters.update_printer import UpdatePrinter
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user