mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-26 16:27:17 +00:00
Compare commits
2 Commits
7885df0dae
...
a1374dd477
Author | SHA1 | Date | |
---|---|---|---|
a1374dd477 | |||
faef091959 |
2
.github/workflows/setup.sh
vendored
2
.github/workflows/setup.sh
vendored
@ -31,7 +31,7 @@ mv "dist/ahriman-$PKGVER.tar.gz" package/archlinux
|
|||||||
chmod +777 package/archlinux # because fuck you that's why
|
chmod +777 package/archlinux # because fuck you that's why
|
||||||
cd package/archlinux
|
cd package/archlinux
|
||||||
sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm
|
sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm
|
||||||
sudo -u nobody -- makepkg --packagelist | grep "ahriman-$PKGVER" | pacman -U --noconfirm --nodeps -
|
sudo -u nobody -- makepkg --packagelist | grep "ahriman-core-$PKGVER" | pacman -U --noconfirm --nodeps -
|
||||||
if [[ -z $MINIMAL_INSTALL ]]; then
|
if [[ -z $MINIMAL_INSTALL ]]; then
|
||||||
sudo -u nobody -- makepkg --packagelist | grep "ahriman-triggers-$PKGVER" | pacman -U --noconfirm --nodeps -
|
sudo -u nobody -- makepkg --packagelist | grep "ahriman-triggers-$PKGVER" | pacman -U --noconfirm --nodeps -
|
||||||
sudo -u nobody -- makepkg --packagelist | grep "ahriman-web-$PKGVER" | pacman -U --noconfirm --nodeps -
|
sudo -u nobody -- makepkg --packagelist | grep "ahriman-web-$PKGVER" | pacman -U --noconfirm --nodeps -
|
||||||
|
@ -27,7 +27,7 @@ Let's imagine, that the new class implements ``help-web``, which prints server i
|
|||||||
result = response.json()
|
result = response.json()
|
||||||
print(result)
|
print(result)
|
||||||
|
|
||||||
The main functionality of the class is already described, but command is still not available yet. To do so, it is required to set ``arguments`` property, which is list of functions, which takes argument parser object, creates new subcommand and returns the modified parser, e.g.:
|
The main functionality of the class is already described, but command is still not available yet. To do so, it is required to set ``arguments`` property, which is the list of the functions, each of them which takes argument parser object, creates new subcommand and returns the modified parser, e.g.:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
Writing own API endpoint
|
Writing own API endpoint
|
||||||
========================
|
========================
|
||||||
|
|
||||||
The web service loads views dynamically, thus it is possible to add custom API endpoint or even web page. The views must be derived from ``ahriman.web.views.base.BaseView`` and implements HTTP methods. The API specification will be also loaded (if available), but optional. The implementation must be saved into the ``ahriman.web.views`` package
|
The web service loads views dynamically, thus it is possible to add custom API endpoint or even web page. The view must be derived from ``ahriman.web.views.base.BaseView`` and should implement desired HTTP methods. The API specification will be also loaded automatically if available, but optional. The implementation must be saved into the ``ahriman.web.views`` package
|
||||||
|
|
||||||
Let's consider example for API endpoint which always returns 204 with no response:
|
Let's consider example of API endpoint which always returns 204 with no response:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@ -18,9 +18,9 @@ Let's consider example for API endpoint which always returns 204 with no respons
|
|||||||
# check public methods of the BaseView class for all available controls
|
# check public methods of the BaseView class for all available controls
|
||||||
raise HTTPNoContent
|
raise HTTPNoContent
|
||||||
|
|
||||||
The ``get()`` method can be decorated by ``aiohttp_apispec`` methods, but we will leave it for self-study. Consider checking examples of usages in the main package.
|
The ``get()`` method can be decorated by ``aiohttp_apispec`` methods, but we will leave it for a self-study, please, consider to check examples of usages in the main package.
|
||||||
|
|
||||||
In order to view to be set correctly to routes list, few more options are required to be set. First of all, it is required to specify ``ROUTES`` (list of strings), which contains list of all available routes, e.g.:
|
In order to view to be added to the route list correctly, few more properties are required to be set. First of all, it is required to specify ``ROUTES`` (list of strings), which contains list of all available routes, e.g.:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@ 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.api`` not a real API, but some views which provide OpenAPI support.
|
||||||
* ``ahriman.web.views.*.auditlog`` provides event log API.
|
* ``ahriman.web.views.*.auditlog`` provides event log API.
|
||||||
* ``ahriman.web.views.*.distributed`` is an API for builders interaction for multi-node setup.
|
* ``ahriman.web.views.*.distributed`` is an API for builders interaction for multi-node setup.
|
||||||
* ``ahriman.web.views.*.pacakges`` contains views which provide information about existing packages.
|
* ``ahriman.web.views.*.packages`` contains views which provide information about existing packages.
|
||||||
* ``ahriman.web.views.*.service`` provides views for application controls.
|
* ``ahriman.web.views.*.service`` provides views for application controls.
|
||||||
* ``ahriman.web.views.*.status`` package provides REST API for application reporting.
|
* ``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.
|
* ``ahriman.web.views.*.user`` package provides login and logout methods which can be called without authorization.
|
||||||
|
@ -13,7 +13,7 @@ TL;DR
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
yay -S ahriman
|
yay -S ahriman-core
|
||||||
ahriman -a x86_64 -r aur service-setup --packager "ahriman bot <ahriman@example.com>"
|
ahriman -a x86_64 -r aur service-setup --packager "ahriman bot <ahriman@example.com>"
|
||||||
systemctl enable --now ahriman@x86_64-aur.timer
|
systemctl enable --now ahriman@x86_64-aur.timer
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ How to setup web service
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
yay -S -ahriman-web
|
yay -S ahriman-web
|
||||||
|
|
||||||
#.
|
#.
|
||||||
Configure service:
|
Configure service:
|
||||||
|
@ -2,7 +2,7 @@ Initial setup
|
|||||||
=============
|
=============
|
||||||
|
|
||||||
#.
|
#.
|
||||||
Install package as usual.
|
Install package(s) as usual. At least, ``ahriman-core`` package is required; other features can be installed separately. Alternatively, it is possible to install meta-package, which includes everything.
|
||||||
#.
|
#.
|
||||||
Change settings if required, see :doc:`configuration reference <configuration>` for more details.
|
Change settings if required, see :doc:`configuration reference <configuration>` for more details.
|
||||||
#.
|
#.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Evgeniy Alekseev
|
# Maintainer: Evgeniy Alekseev
|
||||||
|
|
||||||
pkgbase='ahriman'
|
pkgbase='ahriman'
|
||||||
pkgname=('ahriman' 'ahriman-triggers' 'ahriman-web')
|
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
||||||
pkgver=2.15.2
|
pkgver=2.15.2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
@ -22,6 +22,12 @@ build() {
|
|||||||
|
|
||||||
package_ahriman() {
|
package_ahriman() {
|
||||||
pkgname='ahriman'
|
pkgname='ahriman'
|
||||||
|
pkgdesc="ArcH linux ReposItory MANager (meta package)"
|
||||||
|
depends=("$pkgbase-core=$pkgver" "$pkgbase-triggers=$pkgver" "$pkgbase-web=$pkgver")
|
||||||
|
}
|
||||||
|
|
||||||
|
package_ahriman-core() {
|
||||||
|
pkgname='ahriman-core'
|
||||||
optdepends=('ahriman-triggers: additional extensions for the application'
|
optdepends=('ahriman-triggers: additional extensions for the application'
|
||||||
'ahriman-web: web server'
|
'ahriman-web: web server'
|
||||||
'python-boto3: sync to s3'
|
'python-boto3: sync to s3'
|
||||||
@ -31,7 +37,7 @@ package_ahriman() {
|
|||||||
'python-jinja: html report generation'
|
'python-jinja: html report generation'
|
||||||
'python-systemd: journal support'
|
'python-systemd: journal support'
|
||||||
'rsync: sync by using rsync')
|
'rsync: sync by using rsync')
|
||||||
install="$pkgname.install"
|
install="$pkgbase.install"
|
||||||
backup=('etc/ahriman.ini'
|
backup=('etc/ahriman.ini'
|
||||||
'etc/ahriman.ini.d/logging.ini')
|
'etc/ahriman.ini.d/logging.ini')
|
||||||
|
|
||||||
@ -51,7 +57,7 @@ package_ahriman() {
|
|||||||
package_ahriman-triggers() {
|
package_ahriman-triggers() {
|
||||||
pkgname='ahriman-triggers'
|
pkgname='ahriman-triggers'
|
||||||
pkgdesc="ArcH linux ReposItory MANager, additional extensions"
|
pkgdesc="ArcH linux ReposItory MANager, additional extensions"
|
||||||
depends=("$pkgbase=$pkgver")
|
depends=("$pkgbase-core=$pkgver")
|
||||||
backup=('etc/ahriman.ini.d/00-triggers.ini')
|
backup=('etc/ahriman.ini.d/00-triggers.ini')
|
||||||
|
|
||||||
cd "$pkgbase-$pkgver"
|
cd "$pkgbase-$pkgver"
|
||||||
@ -65,7 +71,7 @@ package_ahriman-triggers() {
|
|||||||
package_ahriman-web() {
|
package_ahriman-web() {
|
||||||
pkgname='ahriman-web'
|
pkgname='ahriman-web'
|
||||||
pkgdesc="ArcH linux ReposItory MANager, web server"
|
pkgdesc="ArcH linux ReposItory MANager, web server"
|
||||||
depends=("$pkgbase=$pkgver" 'python-aiohttp-apispec>=3.0.0' 'python-aiohttp-cors' 'python-aiohttp-jinja2')
|
depends=("$pkgbase-core=$pkgver" 'python-aiohttp-apispec>=3.0.0' 'python-aiohttp-cors' 'python-aiohttp-jinja2')
|
||||||
optdepends=('python-aioauth-client: OAuth2 authorization support'
|
optdepends=('python-aioauth-client: OAuth2 authorization support'
|
||||||
'python-aiohttp-security: authorization support'
|
'python-aiohttp-security: authorization support'
|
||||||
'python-aiohttp-session: authorization support'
|
'python-aiohttp-session: authorization support'
|
||||||
|
@ -2,6 +2,7 @@ post_upgrade() {
|
|||||||
local breakpoints=(
|
local breakpoints=(
|
||||||
2.9.0-1
|
2.9.0-1
|
||||||
2.12.0-1
|
2.12.0-1
|
||||||
|
2.16.0-1
|
||||||
)
|
)
|
||||||
|
|
||||||
for v in "${breakpoints[@]}"; do
|
for v in "${breakpoints[@]}"; do
|
||||||
|
@ -27,6 +27,7 @@ from ahriman.core.configuration import Configuration
|
|||||||
from ahriman.core.spawn import Spawn
|
from ahriman.core.spawn import Spawn
|
||||||
from ahriman.core.triggers import TriggerLoader
|
from ahriman.core.triggers import TriggerLoader
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
from ahriman.web.web import run_server, setup_server
|
||||||
|
|
||||||
|
|
||||||
class Web(Handler):
|
class Web(Handler):
|
||||||
@ -48,9 +49,6 @@ class Web(Handler):
|
|||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
report(bool): force enable or disable reporting
|
report(bool): force enable or disable reporting
|
||||||
"""
|
"""
|
||||||
# we are using local import for optional dependencies
|
|
||||||
from ahriman.web.web import run_server, setup_server
|
|
||||||
|
|
||||||
spawner_args = Web.extract_arguments(args, configuration)
|
spawner_args = Web.extract_arguments(args, configuration)
|
||||||
spawner = Spawn(args.parser(), list(spawner_args))
|
spawner = Spawn(args.parser(), list(spawner_args))
|
||||||
spawner.start()
|
spawner.start()
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import Application, View
|
from aiohttp.web import Application, View
|
||||||
|
from collections.abc import Generator
|
||||||
|
|
||||||
import ahriman.web.views
|
import ahriman.web.views
|
||||||
|
|
||||||
@ -29,22 +30,19 @@ from ahriman.web.views.base import BaseView
|
|||||||
__all__ = ["setup_routes"]
|
__all__ = ["setup_routes"]
|
||||||
|
|
||||||
|
|
||||||
def _dynamic_routes(configuration: Configuration) -> dict[str, type[View]]:
|
def _dynamic_routes(configuration: Configuration) -> Generator[tuple[str, type[View]], None, None]:
|
||||||
"""
|
"""
|
||||||
extract dynamic routes based on views
|
extract dynamic routes based on views
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
|
|
||||||
Returns:
|
Yields:
|
||||||
dict[str, type[View]]: map of the route to its view
|
tuple[str, type[View]]: map of the route to its view
|
||||||
"""
|
"""
|
||||||
routes: dict[str, type[View]] = {}
|
|
||||||
for view in implementations(ahriman.web.views, BaseView):
|
for view in implementations(ahriman.web.views, BaseView):
|
||||||
view_routes = view.routes(configuration)
|
for route in view.routes(configuration):
|
||||||
routes.update([(route, view) for route in view_routes])
|
yield route, view
|
||||||
|
|
||||||
return routes
|
|
||||||
|
|
||||||
|
|
||||||
def setup_routes(application: Application, configuration: Configuration) -> None:
|
def setup_routes(application: Application, configuration: Configuration) -> None:
|
||||||
@ -57,5 +55,5 @@ def setup_routes(application: Application, configuration: Configuration) -> None
|
|||||||
"""
|
"""
|
||||||
application.router.add_static("/static", configuration.getpath("web", "static_path"), follow_symlinks=True)
|
application.router.add_static("/static", configuration.getpath("web", "static_path"), follow_symlinks=True)
|
||||||
|
|
||||||
for route, view in _dynamic_routes(configuration).items():
|
for route, view in _dynamic_routes(configuration):
|
||||||
application.router.add_view(route, view)
|
application.router.add_view(route, view)
|
||||||
|
@ -28,7 +28,7 @@ from pathlib import Path
|
|||||||
prefix = Path(sys.prefix).relative_to("/")
|
prefix = Path(sys.prefix).relative_to("/")
|
||||||
site_packages = Path(site.getsitepackages()[0]).relative_to("/")
|
site_packages = Path(site.getsitepackages()[0]).relative_to("/")
|
||||||
SUBPACKAGES = {
|
SUBPACKAGES = {
|
||||||
"ahriman": [
|
"ahriman-core": [
|
||||||
prefix / "bin",
|
prefix / "bin",
|
||||||
prefix / "lib" / "systemd",
|
prefix / "lib" / "systemd",
|
||||||
prefix / "share",
|
prefix / "share",
|
||||||
|
@ -1 +1,9 @@
|
|||||||
# nothing to test here
|
from ahriman.application.handlers.triggers import Triggers
|
||||||
|
from ahriman.application.handlers.triggers_support import TriggersSupport
|
||||||
|
|
||||||
|
|
||||||
|
def test_arguments() -> None:
|
||||||
|
"""
|
||||||
|
must define own arguments
|
||||||
|
"""
|
||||||
|
assert TriggersSupport.arguments != Triggers.arguments
|
||||||
|
@ -36,8 +36,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
|||||||
"""
|
"""
|
||||||
args = _default_args(args)
|
args = _default_args(args)
|
||||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||||
setup_mock = mocker.patch("ahriman.web.web.setup_server")
|
setup_mock = mocker.patch("ahriman.application.handlers.web.setup_server")
|
||||||
run_mock = mocker.patch("ahriman.web.web.run_server")
|
run_mock = mocker.patch("ahriman.application.handlers.web.run_server")
|
||||||
start_mock = mocker.patch("ahriman.core.spawn.Spawn.start")
|
start_mock = mocker.patch("ahriman.core.spawn.Spawn.start")
|
||||||
trigger_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.load")
|
trigger_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.load")
|
||||||
stop_mock = mocker.patch("ahriman.core.spawn.Spawn.stop")
|
stop_mock = mocker.patch("ahriman.core.spawn.Spawn.stop")
|
||||||
|
@ -6,6 +6,15 @@ from ahriman.core.module_loader import _modules, implementations
|
|||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
def test_modules() -> None:
|
||||||
|
"""
|
||||||
|
must load modules
|
||||||
|
"""
|
||||||
|
modules = list(_modules(Path(__file__).parent.parent, "ahriman.web.views"))
|
||||||
|
assert modules
|
||||||
|
assert all(not module.ispkg for module in modules)
|
||||||
|
|
||||||
|
|
||||||
def test_implementations() -> None:
|
def test_implementations() -> None:
|
||||||
"""
|
"""
|
||||||
must load implementations from the package
|
must load implementations from the package
|
||||||
@ -14,12 +23,3 @@ def test_implementations() -> None:
|
|||||||
assert routes
|
assert routes
|
||||||
assert all(isinstance(view, type) for view in routes)
|
assert all(isinstance(view, type) for view in routes)
|
||||||
assert all(issubclass(view, BaseView) for view in routes)
|
assert all(issubclass(view, BaseView) for view in routes)
|
||||||
|
|
||||||
|
|
||||||
def test_modules() -> None:
|
|
||||||
"""
|
|
||||||
must load modules
|
|
||||||
"""
|
|
||||||
modules = list(_modules(Path(__file__).parent.parent, "ahriman.web.views"))
|
|
||||||
assert modules
|
|
||||||
assert all(not module.ispkg for module in modules)
|
|
||||||
|
@ -17,7 +17,7 @@ def test_dynamic_routes(resource_path_root: Path, configuration: Configuration)
|
|||||||
if file.suffix == ".py" and file.name not in ("__init__.py", "base.py", "status_view_guard.py")
|
if file.suffix == ".py" and file.name not in ("__init__.py", "base.py", "status_view_guard.py")
|
||||||
]
|
]
|
||||||
|
|
||||||
routes = _dynamic_routes(configuration)
|
routes = dict(_dynamic_routes(configuration))
|
||||||
assert all(isinstance(view, type) for view in routes.values())
|
assert all(isinstance(view, type) for view in routes.values())
|
||||||
assert len(set(routes.values())) == len(expected_views)
|
assert len(set(routes.values())) == len(expected_views)
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ async def test_get_not_found(client_with_auth: TestClient, mocker: MockerFixture
|
|||||||
"""
|
"""
|
||||||
must raise not found if path is invalid
|
must raise not found if path is invalid
|
||||||
"""
|
"""
|
||||||
print([route.handler for route in client_with_auth.app.router.routes()])
|
|
||||||
static_route = next(route for route in client_with_auth.app.router.routes() if route.handler == StaticView)
|
static_route = next(route for route in client_with_auth.app.router.routes() if route.handler == StaticView)
|
||||||
mocker.patch.object(static_route.handler, "ROUTES", [])
|
mocker.patch.object(static_route.handler, "ROUTES", [])
|
||||||
response = await client_with_auth.get("/favicon.ico", allow_redirects=False)
|
response = await client_with_auth.get("/favicon.ico", allow_redirects=False)
|
||||||
|
Loading…
Reference in New Issue
Block a user