mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-22 02:09:56 +00:00
use api generated docs instead of comments (#92)
This commit is contained in:
@ -27,7 +27,7 @@ from urllib.parse import urlparse
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
class Validator(RootValidator): # type: ignore
|
||||
class Validator(RootValidator):
|
||||
"""
|
||||
class which defines custom validation methods for the service configuration
|
||||
|
||||
|
@ -19,10 +19,11 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from typing import Callable, Iterable, List, Tuple
|
||||
from typing import Callable, Iterable, List
|
||||
|
||||
from ahriman.core.util import partition
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
@ -149,13 +150,6 @@ class Tree:
|
||||
Returns:
|
||||
List[List[Package]]: sorted list of packages lists based on their dependencies
|
||||
"""
|
||||
# https://docs.python.org/dev/library/itertools.html#itertools-recipes
|
||||
def partition(source: List[Leaf]) -> Tuple[List[Leaf], Iterable[Leaf]]:
|
||||
first_iter, second_iter = itertools.tee(source)
|
||||
filter_fn: Callable[[Leaf], bool] = lambda leaf: leaf.is_dependency(next_level)
|
||||
# materialize first list and leave second as iterator
|
||||
return list(filter(filter_fn, first_iter)), itertools.filterfalse(filter_fn, second_iter)
|
||||
|
||||
unsorted: List[List[Leaf]] = []
|
||||
|
||||
# build initial tree
|
||||
@ -170,7 +164,9 @@ class Tree:
|
||||
next_level = unsorted[next_num]
|
||||
|
||||
# change lists inside the collection
|
||||
unsorted[current_num], to_be_moved = partition(current_level)
|
||||
# additional workaround with partial in order to hide cell-var-from-loop pylint warning
|
||||
predicate = functools.partial(Leaf.is_dependency, packages=next_level)
|
||||
unsorted[current_num], to_be_moved = partition(current_level, predicate)
|
||||
unsorted[next_num].extend(to_be_moved)
|
||||
|
||||
comparator: Callable[[Package], str] = lambda package: package.base
|
||||
|
@ -19,6 +19,7 @@
|
||||
#
|
||||
import datetime
|
||||
import io
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@ -28,14 +29,31 @@ import subprocess
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from pwd import getpwuid
|
||||
from typing import Any, Dict, Generator, IO, Iterable, List, Optional, Type, Union
|
||||
from typing import Any, Callable, Dict, Generator, IO, Iterable, List, Optional, Type, TypeVar, Tuple, Union
|
||||
|
||||
from ahriman.core.exceptions import OptionError, UnsafeRunError
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
__all__ = ["check_output", "check_user", "enum_values", "exception_response_text", "filter_json", "full_version",
|
||||
"package_like", "pretty_datetime", "pretty_size", "safe_filename", "trim_package", "utcnow", "walk"]
|
||||
__all__ = [
|
||||
"check_output",
|
||||
"check_user",
|
||||
"enum_values",
|
||||
"exception_response_text",
|
||||
"filter_json",
|
||||
"full_version",
|
||||
"package_like",
|
||||
"partition",
|
||||
"pretty_datetime",
|
||||
"pretty_size",
|
||||
"safe_filename",
|
||||
"trim_package",
|
||||
"utcnow",
|
||||
"walk",
|
||||
]
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def check_output(*args: str, exception: Optional[Exception] = None, cwd: Optional[Path] = None,
|
||||
@ -225,6 +243,21 @@ def package_like(filename: Path) -> bool:
|
||||
return ".pkg." in name and not name.endswith(".sig")
|
||||
|
||||
|
||||
def partition(source: List[T], predicate: Callable[[T], bool]) -> Tuple[List[T], List[T]]:
|
||||
"""
|
||||
partition list into two based on predicate, based on # https://docs.python.org/dev/library/itertools.html#itertools-recipes
|
||||
|
||||
Args:
|
||||
source(List[T]): source list to be partitioned
|
||||
predicate(Callable[[T], bool]): filter function
|
||||
|
||||
Returns:
|
||||
Tuple[List[T], List[T]]: two lists, first is which ``predicate`` is ``True``, second is ``False``
|
||||
"""
|
||||
first_iter, second_iter = itertools.tee(source)
|
||||
return list(filter(predicate, first_iter)), list(itertools.filterfalse(predicate, second_iter))
|
||||
|
||||
|
||||
def pretty_datetime(timestamp: Optional[Union[datetime.datetime, float, int]]) -> str:
|
||||
"""
|
||||
convert datetime object to string
|
||||
|
120
src/ahriman/web/apispec.py
Normal file
120
src/ahriman/web/apispec.py
Normal file
@ -0,0 +1,120 @@
|
||||
#
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import Application
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from ahriman import version
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
__all__ = ["setup_apispec"]
|
||||
|
||||
|
||||
def _info() -> Dict[str, Any]:
|
||||
"""
|
||||
create info object for swagger docs
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: info object as per openapi specification
|
||||
"""
|
||||
return {
|
||||
"title": "ahriman",
|
||||
"description": """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 support.
|
||||
* Dependency manager.
|
||||
* VCS packages support.
|
||||
* Official repository support.
|
||||
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
||||
* Sign support with gpg (repository, package, per package settings).
|
||||
* Triggers for repository updates, e.g. synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram).
|
||||
* Repository status interface with optional authorization and control options
|
||||
|
||||
<security-definitions />
|
||||
""",
|
||||
"license": {
|
||||
"name": "GPL3",
|
||||
"url": "https://raw.githubusercontent.com/arcan1s/ahriman/master/LICENSE",
|
||||
},
|
||||
"version": version.__version__,
|
||||
}
|
||||
|
||||
|
||||
def _security() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
get security definitions
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: generated security definition
|
||||
"""
|
||||
return [{
|
||||
"token": {
|
||||
"type": "apiKey", # as per specification we are using api key
|
||||
"name": "API_SESSION",
|
||||
"in": "cookie",
|
||||
}
|
||||
}]
|
||||
|
||||
|
||||
def _servers(application: Application) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
get list of defined addresses for server
|
||||
|
||||
Args:
|
||||
application(Application): web application instance
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: list (actually only one) of defined web urls
|
||||
"""
|
||||
configuration: Configuration = application["configuration"]
|
||||
address = configuration.get("web", "address", fallback=None)
|
||||
if not address:
|
||||
host = configuration.get("web", "host")
|
||||
port = configuration.getint("web", "port")
|
||||
address = f"http://{host}:{port}"
|
||||
|
||||
return [{
|
||||
"url": address,
|
||||
}]
|
||||
|
||||
|
||||
def setup_apispec(application: Application) -> aiohttp_apispec.AiohttpApiSpec:
|
||||
"""
|
||||
setup swagger api specification
|
||||
|
||||
Args:
|
||||
application(Application): web application instance
|
||||
|
||||
Returns:
|
||||
aiohttp_apispec.AiohttpApiSpec: created specification instance
|
||||
"""
|
||||
return aiohttp_apispec.setup_aiohttp_apispec(
|
||||
application,
|
||||
url="/api-docs/swagger.json",
|
||||
openapi_version="3.0.2",
|
||||
info=_info(),
|
||||
servers=_servers(application),
|
||||
security=_security(),
|
||||
)
|
48
src/ahriman/web/cors.py
Normal file
48
src/ahriman/web/cors.py
Normal file
@ -0,0 +1,48 @@
|
||||
#
|
||||
# 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 aiohttp_cors # type: ignore
|
||||
|
||||
from aiohttp.web import Application
|
||||
|
||||
|
||||
__all__ = ["setup_cors"]
|
||||
|
||||
|
||||
def setup_cors(application: Application) -> aiohttp_cors.CorsConfig:
|
||||
"""
|
||||
setup CORS for the web application
|
||||
|
||||
Args:
|
||||
application(Application): web application instance
|
||||
|
||||
Returns:
|
||||
aiohttp_cors.CorsConfig: generated CORS configuration
|
||||
"""
|
||||
cors = aiohttp_cors.setup(application, defaults={
|
||||
"*": aiohttp_cors.ResourceOptions(
|
||||
expose_headers="*",
|
||||
allow_headers="*",
|
||||
allow_methods="*",
|
||||
)
|
||||
})
|
||||
for route in application.router.routes():
|
||||
cors.add(route)
|
||||
|
||||
return cors
|
@ -17,8 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp.web import Request
|
||||
from aiohttp.web_response import StreamResponse
|
||||
from aiohttp.web import Request, StreamResponse
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
|
||||
|
@ -21,10 +21,7 @@ import aiohttp_security # type: ignore
|
||||
import socket
|
||||
import types
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.web import middleware, Request
|
||||
from aiohttp.web_response import StreamResponse
|
||||
from aiohttp.web_urldispatcher import StaticResource
|
||||
from aiohttp.web import middleware, Application, Request, StaticResource, StreamResponse
|
||||
from aiohttp_session import setup as setup_session
|
||||
from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
||||
from cryptography import fernet
|
||||
@ -36,10 +33,10 @@ from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.middlewares import HandlerType, MiddlewareType
|
||||
|
||||
|
||||
__all__ = ["AuthorizationPolicy", "auth_handler", "cookie_secret_key", "setup_auth"]
|
||||
__all__ = ["setup_auth"]
|
||||
|
||||
|
||||
class AuthorizationPolicy(aiohttp_security.AbstractAuthorizationPolicy): # type: ignore
|
||||
class _AuthorizationPolicy(aiohttp_security.AbstractAuthorizationPolicy):
|
||||
"""
|
||||
authorization policy implementation
|
||||
|
||||
@ -83,7 +80,7 @@ class AuthorizationPolicy(aiohttp_security.AbstractAuthorizationPolicy): # type
|
||||
return await self.validator.verify_access(identity, permission, context)
|
||||
|
||||
|
||||
def auth_handler(allow_read_only: bool) -> MiddlewareType:
|
||||
def _auth_handler(allow_read_only: bool) -> MiddlewareType:
|
||||
"""
|
||||
authorization and authentication middleware
|
||||
|
||||
@ -118,7 +115,7 @@ def auth_handler(allow_read_only: bool) -> MiddlewareType:
|
||||
return handle
|
||||
|
||||
|
||||
def cookie_secret_key(configuration: Configuration) -> fernet.Fernet:
|
||||
def _cookie_secret_key(configuration: Configuration) -> fernet.Fernet:
|
||||
"""
|
||||
extract cookie secret key from configuration if set or generate new one
|
||||
|
||||
@ -135,26 +132,26 @@ def cookie_secret_key(configuration: Configuration) -> fernet.Fernet:
|
||||
return fernet.Fernet(secret_key)
|
||||
|
||||
|
||||
def setup_auth(application: web.Application, configuration: Configuration, validator: Auth) -> web.Application:
|
||||
def setup_auth(application: Application, configuration: Configuration, validator: Auth) -> Application:
|
||||
"""
|
||||
setup authorization policies for the application
|
||||
|
||||
Args:
|
||||
application(web.Application): web application instance
|
||||
application(Application): web application instance
|
||||
configuration(Configuration): configuration instance
|
||||
validator(Auth): authorization module instance
|
||||
|
||||
Returns:
|
||||
web.Application: configured web application
|
||||
Application: configured web application
|
||||
"""
|
||||
secret_key = cookie_secret_key(configuration)
|
||||
secret_key = _cookie_secret_key(configuration)
|
||||
storage = EncryptedCookieStorage(secret_key, cookie_name="API_SESSION", max_age=validator.max_age)
|
||||
setup_session(application, storage)
|
||||
|
||||
authorization_policy = AuthorizationPolicy(validator)
|
||||
authorization_policy = _AuthorizationPolicy(validator)
|
||||
identity_policy = aiohttp_security.SessionIdentityPolicy()
|
||||
|
||||
aiohttp_security.setup(application, identity_policy, authorization_policy)
|
||||
application.middlewares.append(auth_handler(validator.allow_read_only))
|
||||
application.middlewares.append(_auth_handler(validator.allow_read_only))
|
||||
|
||||
return application
|
||||
|
@ -20,8 +20,8 @@
|
||||
import aiohttp_jinja2
|
||||
import logging
|
||||
|
||||
from aiohttp.web import HTTPClientError, HTTPException, HTTPServerError, HTTPUnauthorized, Request, StreamResponse, \
|
||||
json_response, middleware
|
||||
from aiohttp.web import HTTPClientError, HTTPException, HTTPMethodNotAllowed, HTTPNoContent, HTTPServerError, \
|
||||
HTTPUnauthorized, Request, StreamResponse, json_response, middleware
|
||||
|
||||
from ahriman.web.middlewares import HandlerType, MiddlewareType
|
||||
|
||||
@ -29,6 +29,20 @@ from ahriman.web.middlewares import HandlerType, MiddlewareType
|
||||
__all__ = ["exception_handler"]
|
||||
|
||||
|
||||
def _is_templated_unauthorized(request: Request) -> bool:
|
||||
"""
|
||||
check if the request is eligible for rendering html template
|
||||
|
||||
Args:
|
||||
request(Request): source request to check
|
||||
|
||||
Returns:
|
||||
bool: True in case if response should be rendered as html and False otherwise
|
||||
"""
|
||||
return request.path in ("/api/v1/login", "/api/v1/logout") \
|
||||
and "application/json" not in request.headers.getall("accept", [])
|
||||
|
||||
|
||||
def exception_handler(logger: logging.Logger) -> MiddlewareType:
|
||||
"""
|
||||
exception handler middleware. Just log any exception (except for client ones)
|
||||
@ -44,10 +58,21 @@ def exception_handler(logger: logging.Logger) -> MiddlewareType:
|
||||
try:
|
||||
return await handler(request)
|
||||
except HTTPUnauthorized as e:
|
||||
if is_templated_unauthorized(request):
|
||||
if _is_templated_unauthorized(request):
|
||||
context = {"code": e.status_code, "reason": e.reason}
|
||||
return aiohttp_jinja2.render_template("error.jinja2", request, context, status=e.status_code)
|
||||
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||
except HTTPMethodNotAllowed as e:
|
||||
if e.method == "OPTIONS":
|
||||
# automatically handle OPTIONS method, idea comes from
|
||||
# https://github.com/arcan1s/ffxivbis/blob/master/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala#L32
|
||||
raise HTTPNoContent(headers={"Allow": ",".join(sorted(e.allowed_methods))})
|
||||
if e.method == "HEAD":
|
||||
# since we have special autogenerated HEAD method, we need to remove it from list of available
|
||||
e.allowed_methods = {method for method in e.allowed_methods if method != "HEAD"}
|
||||
e.headers["Allow"] = ",".join(sorted(e.allowed_methods))
|
||||
raise e
|
||||
raise
|
||||
except HTTPClientError as e:
|
||||
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||
except HTTPServerError as e:
|
||||
@ -60,17 +85,3 @@ def exception_handler(logger: logging.Logger) -> MiddlewareType:
|
||||
return json_response(data={"error": str(e)}, status=500)
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def is_templated_unauthorized(request: Request) -> bool:
|
||||
"""
|
||||
check if the request is eligible for rendering html template
|
||||
|
||||
Args:
|
||||
request(Request): source request to check
|
||||
|
||||
Returns:
|
||||
bool: True in case if response should be rendered as html and False otherwise
|
||||
"""
|
||||
return request.path in ("/api/v1/login", "/api/v1/logout") \
|
||||
and "application/json" not in request.headers.getall("accept", [])
|
||||
|
@ -20,6 +20,8 @@
|
||||
from aiohttp.web import Application
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.web.views.api.docs import DocsView
|
||||
from ahriman.web.views.api.swagger import SwaggerView
|
||||
from ahriman.web.views.index import IndexView
|
||||
from ahriman.web.views.service.add import AddView
|
||||
from ahriman.web.views.service.pgp import PGPView
|
||||
@ -43,82 +45,31 @@ def setup_routes(application: Application, static_path: Path) -> None:
|
||||
"""
|
||||
setup all defined routes
|
||||
|
||||
Available routes are:
|
||||
|
||||
* ``GET /`` get build status page
|
||||
* ``GET /index.html`` same as above
|
||||
|
||||
* ``POST /api/v1/service/add`` add new packages to repository
|
||||
|
||||
* ``GET /api/v1/service/pgp`` fetch PGP key from the keyserver
|
||||
* ``POST /api/v1/service/pgp`` import PGP key from the keyserver
|
||||
|
||||
* ``POST /api/v1/service/rebuild`` rebuild packages based on their dependency list
|
||||
|
||||
* ``POST /api/v1/service/remove`` remove existing package from repository
|
||||
|
||||
* ``POST /api/v1/service/request`` request to add new packages to repository
|
||||
|
||||
* ``GET /api/v1/service/search`` search for substring in AUR
|
||||
|
||||
* ``POST /api/v1/service/update`` update all packages in repository
|
||||
|
||||
* ``GET /api/v1/packages`` get all known packages
|
||||
* ``POST /api/v1/packages`` force update every package from repository
|
||||
|
||||
* ``DELETE /api/v1/package/:base`` delete package base from status page
|
||||
* ``GET /api/v1/package/:base`` get package base status
|
||||
* ``POST /api/v1/package/:base`` update package base status
|
||||
|
||||
* ``DELETE /api/v1/packages/{package}/logs`` delete package related logs
|
||||
* ``GET /api/v1/packages/{package}/logs`` create log record for the package
|
||||
* ``POST /api/v1/packages/{package}/logs`` get last package logs
|
||||
|
||||
* ``GET /api/v1/status`` get service status itself
|
||||
* ``POST /api/v1/status`` update service status itself
|
||||
|
||||
* ``GET /api/v1/login`` OAuth2 handler for login
|
||||
* ``POST /api/v1/login`` login to service
|
||||
* ``POST /api/v1/logout`` logout from service
|
||||
|
||||
Args:
|
||||
application(Application): web application instance
|
||||
static_path(Path): path to static files directory
|
||||
"""
|
||||
application.router.add_get("/", IndexView, allow_head=True)
|
||||
application.router.add_get("/index.html", IndexView, allow_head=True)
|
||||
application.router.add_view("/", IndexView)
|
||||
application.router.add_view("/index.html", IndexView)
|
||||
|
||||
application.router.add_view("/api-docs", DocsView)
|
||||
application.router.add_view("/api-docs/swagger.json", SwaggerView)
|
||||
|
||||
application.router.add_static("/static", static_path, follow_symlinks=True)
|
||||
|
||||
application.router.add_post("/api/v1/service/add", AddView)
|
||||
application.router.add_view("/api/v1/service/add", AddView)
|
||||
application.router.add_view("/api/v1/service/pgp", PGPView)
|
||||
application.router.add_view("/api/v1/service/rebuild", RebuildView)
|
||||
application.router.add_view("/api/v1/service/remove", RemoveView)
|
||||
application.router.add_view("/api/v1/service/request", RequestView)
|
||||
application.router.add_view("/api/v1/service/search", SearchView)
|
||||
application.router.add_view("/api/v1/service/update", UpdateView)
|
||||
|
||||
application.router.add_get("/api/v1/service/pgp", PGPView, allow_head=True)
|
||||
application.router.add_post("/api/v1/service/pgp", PGPView)
|
||||
application.router.add_view("/api/v1/packages", PackagesView)
|
||||
application.router.add_view("/api/v1/packages/{package}", PackageView)
|
||||
application.router.add_view("/api/v1/packages/{package}/logs", LogsView)
|
||||
|
||||
application.router.add_post("/api/v1/service/rebuild", RebuildView)
|
||||
application.router.add_view("/api/v1/status", StatusView)
|
||||
|
||||
application.router.add_post("/api/v1/service/remove", RemoveView)
|
||||
|
||||
application.router.add_post("/api/v1/service/request", RequestView)
|
||||
|
||||
application.router.add_get("/api/v1/service/search", SearchView, allow_head=False)
|
||||
|
||||
application.router.add_post("/api/v1/service/update", UpdateView)
|
||||
|
||||
application.router.add_get("/api/v1/packages", PackagesView, allow_head=True)
|
||||
application.router.add_post("/api/v1/packages", PackagesView)
|
||||
|
||||
application.router.add_delete("/api/v1/packages/{package}", PackageView)
|
||||
application.router.add_get("/api/v1/packages/{package}", PackageView, allow_head=True)
|
||||
application.router.add_post("/api/v1/packages/{package}", PackageView)
|
||||
|
||||
application.router.add_delete("/api/v1/packages/{package}/logs", LogsView)
|
||||
application.router.add_get("/api/v1/packages/{package}/logs", LogsView, allow_head=True)
|
||||
application.router.add_post("/api/v1/packages/{package}/logs", LogsView)
|
||||
|
||||
application.router.add_get("/api/v1/status", StatusView, allow_head=True)
|
||||
application.router.add_post("/api/v1/status", StatusView)
|
||||
|
||||
application.router.add_get("/api/v1/login", LoginView)
|
||||
application.router.add_post("/api/v1/login", LoginView)
|
||||
application.router.add_post("/api/v1/logout", LogoutView)
|
||||
application.router.add_view("/api/v1/login", LoginView)
|
||||
application.router.add_view("/api/v1/logout", LogoutView)
|
||||
|
19
src/ahriman/web/schemas/__init__.py
Normal file
19
src/ahriman/web/schemas/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
35
src/ahriman/web/schemas/aur_package_schema.py
Normal file
35
src/ahriman/web/schemas/aur_package_schema.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class AURPackageSchema(Schema):
|
||||
"""
|
||||
response AUR package schema
|
||||
"""
|
||||
|
||||
package = fields.String(required=True, metadata={
|
||||
"description": "Package base",
|
||||
"example": "ahriman",
|
||||
})
|
||||
description = fields.String(required=True, metadata={
|
||||
"description": "Package description",
|
||||
"example": "ArcH linux ReposItory MANager",
|
||||
})
|
30
src/ahriman/web/schemas/auth_schema.py
Normal file
30
src/ahriman/web/schemas/auth_schema.py
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class AuthSchema(Schema):
|
||||
"""
|
||||
request cookie authorization schema
|
||||
"""
|
||||
|
||||
API_SESSION = fields.String(required=True, metadata={
|
||||
"description": "API session key as returned from authorization",
|
||||
})
|
51
src/ahriman/web/schemas/counters_schema.py
Normal file
51
src/ahriman/web/schemas/counters_schema.py
Normal file
@ -0,0 +1,51 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class CountersSchema(Schema):
|
||||
"""
|
||||
response package counters schema
|
||||
"""
|
||||
|
||||
total = fields.Integer(required=True, metadata={
|
||||
"description": "Total amount of packages",
|
||||
"example": 6,
|
||||
})
|
||||
_unknown = fields.Integer(data_key="unknown", required=True, metadata={
|
||||
"description": "Amount of packages in unknown state",
|
||||
"example": 0,
|
||||
})
|
||||
pending = fields.Integer(required=True, metadata={
|
||||
"description": "Amount of packages in pending state",
|
||||
"example": 2,
|
||||
})
|
||||
building = fields.Integer(required=True, metadata={
|
||||
"description": "Amount of packages in building state",
|
||||
"example": 1,
|
||||
})
|
||||
failed = fields.Integer(required=True, metadata={
|
||||
"description": "Amount of packages in failed state",
|
||||
"example": 1,
|
||||
})
|
||||
success = fields.Integer(required=True, metadata={
|
||||
"description": "Amount of packages in success state",
|
||||
"example": 3,
|
||||
})
|
30
src/ahriman/web/schemas/error_schema.py
Normal file
30
src/ahriman/web/schemas/error_schema.py
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class ErrorSchema(Schema):
|
||||
"""
|
||||
response error schema
|
||||
"""
|
||||
|
||||
error = fields.String(required=True, metadata={
|
||||
"description": "Error description",
|
||||
})
|
49
src/ahriman/web/schemas/internal_status_schema.py
Normal file
49
src/ahriman/web/schemas/internal_status_schema.py
Normal file
@ -0,0 +1,49 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
from ahriman import version
|
||||
from ahriman.web.schemas.counters_schema import CountersSchema
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
|
||||
|
||||
class InternalStatusSchema(Schema):
|
||||
"""
|
||||
response service status schema
|
||||
"""
|
||||
|
||||
architecture = fields.String(required=True, metadata={
|
||||
"description": "Repository architecture",
|
||||
"example": "x86_64",
|
||||
})
|
||||
packages = fields.Nested(CountersSchema, required=True, metadata={
|
||||
"description": "Repository package counters",
|
||||
})
|
||||
repository = fields.String(required=True, metadata={
|
||||
"description": "Repository name",
|
||||
"example": "repo-clone",
|
||||
})
|
||||
status = fields.Nested(StatusSchema, required=True, metadata={
|
||||
"description": "Repository status as stored by web service",
|
||||
})
|
||||
version = fields.String(required=True, metadata={
|
||||
"description": "Repository version",
|
||||
"example": version.__version__,
|
||||
})
|
38
src/ahriman/web/schemas/log_schema.py
Normal file
38
src/ahriman/web/schemas/log_schema.py
Normal file
@ -0,0 +1,38 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class LogSchema(Schema):
|
||||
"""
|
||||
request package log schema
|
||||
"""
|
||||
|
||||
created = fields.Float(required=True, metadata={
|
||||
"description": "Log record timestamp",
|
||||
"example": 1680537091.233495,
|
||||
})
|
||||
process_id = fields.Integer(required=True, metadata={
|
||||
"description": "Current process id",
|
||||
"example": 42,
|
||||
})
|
||||
message = fields.String(required=True, metadata={
|
||||
"description": "Log message",
|
||||
})
|
35
src/ahriman/web/schemas/login_schema.py
Normal file
35
src/ahriman/web/schemas/login_schema.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class LoginSchema(Schema):
|
||||
"""
|
||||
request login schema
|
||||
"""
|
||||
|
||||
username = fields.String(required=True, metadata={
|
||||
"description": "Login username",
|
||||
"example": "user",
|
||||
})
|
||||
password = fields.String(required=True, metadata={
|
||||
"description": "Login password",
|
||||
"example": "pa55w0rd",
|
||||
})
|
39
src/ahriman/web/schemas/logs_schema.py
Normal file
39
src/ahriman/web/schemas/logs_schema.py
Normal file
@ -0,0 +1,39 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
|
||||
|
||||
class LogsSchema(Schema):
|
||||
"""
|
||||
response package logs schema
|
||||
"""
|
||||
|
||||
package_base = fields.String(required=True, metadata={
|
||||
"description": "Package base name",
|
||||
"example": "ahriman",
|
||||
})
|
||||
status = fields.Nested(StatusSchema, required=True, metadata={
|
||||
"description": "Last package status",
|
||||
})
|
||||
logs = fields.String(required=True, metadata={
|
||||
"description": "Full package log from the last build",
|
||||
})
|
30
src/ahriman/web/schemas/oauth2_schema.py
Normal file
30
src/ahriman/web/schemas/oauth2_schema.py
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class OAuth2Schema(Schema):
|
||||
"""
|
||||
request OAuth2 authorization schema
|
||||
"""
|
||||
|
||||
code = fields.String(metadata={
|
||||
"description": "OAuth2 authorization code. In case if not set, the redirect to provider will be initiated",
|
||||
})
|
31
src/ahriman/web/schemas/package_name_schema.py
Normal file
31
src/ahriman/web/schemas/package_name_schema.py
Normal file
@ -0,0 +1,31 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class PackageNameSchema(Schema):
|
||||
"""
|
||||
request package name schema
|
||||
"""
|
||||
|
||||
package = fields.String(required=True, metadata={
|
||||
"description": "Package name",
|
||||
"example": "ahriman",
|
||||
})
|
31
src/ahriman/web/schemas/package_names_schema.py
Normal file
31
src/ahriman/web/schemas/package_names_schema.py
Normal file
@ -0,0 +1,31 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class PackageNamesSchema(Schema):
|
||||
"""
|
||||
request package names schema
|
||||
"""
|
||||
|
||||
packages = fields.List(fields.String(), required=True, metadata={
|
||||
"description": "Package names",
|
||||
"example": ["ahriman"],
|
||||
})
|
79
src/ahriman/web/schemas/package_properties_schema.py
Normal file
79
src/ahriman/web/schemas/package_properties_schema.py
Normal file
@ -0,0 +1,79 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class PackagePropertiesSchema(Schema):
|
||||
"""
|
||||
request and response package properties schema
|
||||
"""
|
||||
|
||||
architecture = fields.String(metadata={
|
||||
"description": "Package architecture",
|
||||
"example": "x86_64",
|
||||
})
|
||||
archive_size = fields.Integer(metadata={
|
||||
"description": "Archive size in bytes",
|
||||
"example": 287989,
|
||||
})
|
||||
build_date = fields.Integer(metadata={
|
||||
"description": "Package build timestamp",
|
||||
"example": 1680537091,
|
||||
})
|
||||
depends = fields.List(fields.String(), metadata={
|
||||
"description": "Package dependencies list",
|
||||
"example": ["devtools"],
|
||||
})
|
||||
make_depends = fields.List(fields.String(), metadata={
|
||||
"description": "Package make dependencies list",
|
||||
"example": ["python-build"],
|
||||
})
|
||||
opt_depends = fields.List(fields.String(), metadata={
|
||||
"description": "Package optional dependencies list",
|
||||
"example": ["python-aiohttp"],
|
||||
})
|
||||
description = fields.String(metadata={
|
||||
"description": "Package description",
|
||||
"example": "ArcH linux ReposItory MANager",
|
||||
})
|
||||
filename = fields.String(metadata={
|
||||
"description": "Package file name",
|
||||
"example": "ahriman-2.7.1-1-any.pkg.tar.zst",
|
||||
})
|
||||
groups = fields.List(fields.String(), metadata={
|
||||
"description": "Package groups",
|
||||
"example": ["base-devel"],
|
||||
})
|
||||
installed_size = fields.Integer(metadata={
|
||||
"description": "Installed package size in bytes",
|
||||
"example": 2047658,
|
||||
})
|
||||
licenses = fields.List(fields.String(), metadata={
|
||||
"description": "Package licenses",
|
||||
"example": ["GPL3"],
|
||||
})
|
||||
provides = fields.List(fields.String(), metadata={
|
||||
"description": "Package provides list",
|
||||
"example": ["ahriman-git"],
|
||||
})
|
||||
url = fields.String(metadata={
|
||||
"description": "Upstream url",
|
||||
"example": "https://github.com/arcan1s/ahriman",
|
||||
})
|
46
src/ahriman/web/schemas/package_schema.py
Normal file
46
src/ahriman/web/schemas/package_schema.py
Normal file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
from ahriman import version
|
||||
from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema
|
||||
from ahriman.web.schemas.remote_schema import RemoteSchema
|
||||
|
||||
|
||||
class PackageSchema(Schema):
|
||||
"""
|
||||
request and response package schema
|
||||
"""
|
||||
|
||||
base = fields.String(required=True, metadata={
|
||||
"description": "Package base",
|
||||
"example": "ahriman",
|
||||
})
|
||||
version = fields.String(required=True, metadata={
|
||||
"description": "Package version",
|
||||
"example": version.__version__,
|
||||
})
|
||||
remote = fields.Nested(RemoteSchema, required=True, metadata={
|
||||
"description": "Package remote properties",
|
||||
})
|
||||
packages = fields.Dict(
|
||||
keys=fields.String(), values=fields.Nested(PackagePropertiesSchema), required=True, metadata={
|
||||
"description": "Packages which belong to this base",
|
||||
})
|
50
src/ahriman/web/schemas/package_status_schema.py
Normal file
50
src/ahriman/web/schemas/package_status_schema.py
Normal file
@ -0,0 +1,50 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.web.schemas.package_schema import PackageSchema
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
|
||||
|
||||
class PackageStatusSimplifiedSchema(Schema):
|
||||
"""
|
||||
special request package status schema
|
||||
"""
|
||||
|
||||
package = fields.Nested(PackageSchema, metadata={
|
||||
"description": "Package description",
|
||||
})
|
||||
status = fields.Enum(BuildStatusEnum, by_value=True, required=True, metadata={
|
||||
"description": "Current status",
|
||||
})
|
||||
|
||||
|
||||
class PackageStatusSchema(Schema):
|
||||
"""
|
||||
response package status schema
|
||||
"""
|
||||
|
||||
package = fields.Nested(PackageSchema, required=True, metadata={
|
||||
"description": "Package description",
|
||||
})
|
||||
status = fields.Nested(StatusSchema, required=True, metadata={
|
||||
"description": "Last package status",
|
||||
})
|
35
src/ahriman/web/schemas/pgp_key_id_schema.py
Normal file
35
src/ahriman/web/schemas/pgp_key_id_schema.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class PGPKeyIdSchema(Schema):
|
||||
"""
|
||||
request PGP key ID schema
|
||||
"""
|
||||
|
||||
key = fields.String(required=True, metadata={
|
||||
"description": "PGP key ID",
|
||||
"example": "0xE989490C",
|
||||
})
|
||||
server = fields.String(required=True, metadata={
|
||||
"description": "PGP key server",
|
||||
"example": "keyserver.ubuntu.com",
|
||||
})
|
30
src/ahriman/web/schemas/pgp_key_schema.py
Normal file
30
src/ahriman/web/schemas/pgp_key_schema.py
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class PGPKeySchema(Schema):
|
||||
"""
|
||||
response PGP key schema
|
||||
"""
|
||||
|
||||
key = fields.String(required=True, metadata={
|
||||
"description": "PGP key body",
|
||||
})
|
48
src/ahriman/web/schemas/remote_schema.py
Normal file
48
src/ahriman/web/schemas/remote_schema.py
Normal file
@ -0,0 +1,48 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
class RemoteSchema(Schema):
|
||||
"""
|
||||
request and response package remote schema
|
||||
"""
|
||||
|
||||
branch = fields.String(required=True, metadata={
|
||||
"description": "Repository branch",
|
||||
"example": "master",
|
||||
})
|
||||
git_url = fields.String(required=True, metadata={
|
||||
"description": "Package git url",
|
||||
"example": "https://aur.archlinux.org/ahriman.git",
|
||||
})
|
||||
path = fields.String(required=True, metadata={
|
||||
"description": "Path to package sources in git repository",
|
||||
"example": ".",
|
||||
})
|
||||
source = fields.Enum(PackageSource, by_value=True, required=True, metadata={
|
||||
"description": "Pacakge source",
|
||||
})
|
||||
web_url = fields.String(required=True, metadata={
|
||||
"description": "Package repository page",
|
||||
"example": "https://aur.archlinux.org/packages/ahriman",
|
||||
})
|
31
src/ahriman/web/schemas/search_schema.py
Normal file
31
src/ahriman/web/schemas/search_schema.py
Normal file
@ -0,0 +1,31 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
|
||||
class SearchSchema(Schema):
|
||||
"""
|
||||
request package search schema
|
||||
"""
|
||||
|
||||
_for = fields.List(fields.String(), data_key="for", required=True, metadata={
|
||||
"description": "Keyword for search",
|
||||
"example": ["ahriman"],
|
||||
})
|
36
src/ahriman/web/schemas/status_schema.py
Normal file
36
src/ahriman/web/schemas/status_schema.py
Normal file
@ -0,0 +1,36 @@
|
||||
#
|
||||
# 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 marshmallow import Schema, fields
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
|
||||
|
||||
class StatusSchema(Schema):
|
||||
"""
|
||||
request and response status schema
|
||||
"""
|
||||
|
||||
status = fields.Enum(BuildStatusEnum, by_value=True, required=True, metadata={
|
||||
"description": "Current status",
|
||||
})
|
||||
timestamp = fields.Integer(metadata={
|
||||
"description": "Last update timestamp",
|
||||
"example": 1680537091,
|
||||
})
|
19
src/ahriman/web/views/api/__init__.py
Normal file
19
src/ahriman/web/views/api/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
46
src/ahriman/web/views/api/docs.py
Normal file
46
src/ahriman/web/views/api/docs.py
Normal file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# 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 aiohttp_jinja2
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class DocsView(BaseView):
|
||||
"""
|
||||
api docs view
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Unauthorized
|
||||
|
||||
@aiohttp_jinja2.template("api.jinja2")
|
||||
async def get(self) -> Dict[str, Any]:
|
||||
"""
|
||||
return static docs html
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: parameters for jinja template
|
||||
"""
|
||||
return {}
|
76
src/ahriman/web/views/api/swagger.py
Normal file
76
src/ahriman/web/views/api/swagger.py
Normal file
@ -0,0 +1,76 @@
|
||||
#
|
||||
# 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 aiohttp.web import Response, json_response
|
||||
from typing import Callable, Dict
|
||||
|
||||
from ahriman.core.util import partition
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class SwaggerView(BaseView):
|
||||
"""
|
||||
api docs specification view
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Unauthorized
|
||||
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get api specification
|
||||
|
||||
Returns:
|
||||
Response: 200 with json api specification
|
||||
"""
|
||||
spec = self.request.app["swagger_dict"]
|
||||
is_body_parameter: Callable[[Dict[str, str]], bool] = lambda p: p["in"] == "body"
|
||||
|
||||
# special workaround because it writes request body to parameters section
|
||||
paths = spec["paths"]
|
||||
for methods in paths.values():
|
||||
for method in methods.values():
|
||||
if "parameters" not in method:
|
||||
continue
|
||||
|
||||
body, other = partition(method["parameters"], is_body_parameter)
|
||||
if not body:
|
||||
continue # there were no ``body`` parameters found
|
||||
|
||||
# there should be only one body parameters
|
||||
method["requestBody"] = {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": next(iter(body))["schema"]
|
||||
}
|
||||
}
|
||||
}
|
||||
method["parameters"] = other
|
||||
|
||||
# inject security schema
|
||||
spec["components"]["securitySchemes"] = {
|
||||
key: value
|
||||
for schema in spec["security"]
|
||||
for key, value in schema.items()
|
||||
}
|
||||
|
||||
return json_response(spec)
|
@ -19,8 +19,9 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
from aiohttp.web import Request, View
|
||||
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar
|
||||
from aiohttp_cors import CorsViewMixin # type: ignore
|
||||
from aiohttp.web import Request, StreamResponse, View
|
||||
from typing import Any, Awaitable, Callable, Dict, List, Optional, Type, TypeVar
|
||||
|
||||
from ahriman.core.auth import Auth
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -28,15 +29,19 @@ from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
T = TypeVar("T", str, List[str])
|
||||
|
||||
|
||||
class BaseView(View):
|
||||
class BaseView(View, CorsViewMixin):
|
||||
"""
|
||||
base web view to make things typed
|
||||
|
||||
Attributes:
|
||||
OPTIONS_PERMISSION(UserAccess): (class attribute) options permissions of self
|
||||
"""
|
||||
|
||||
OPTIONS_PERMISSION = UserAccess.Unauthorized
|
||||
|
||||
@property
|
||||
def configuration(self) -> Configuration:
|
||||
"""
|
||||
@ -92,7 +97,8 @@ class BaseView(View):
|
||||
Returns:
|
||||
UserAccess: extracted permission
|
||||
"""
|
||||
permission: UserAccess = getattr(cls, f"{request.method.upper()}_PERMISSION", UserAccess.Full)
|
||||
method = "GET" if (other := request.method.upper()) == "HEAD" else other
|
||||
permission: UserAccess = getattr(cls, f"{method}_PERMISSION", UserAccess.Full)
|
||||
return permission
|
||||
|
||||
@staticmethod
|
||||
@ -118,23 +124,6 @@ class BaseView(View):
|
||||
raise KeyError(f"Key {key} is missing or empty")
|
||||
return value
|
||||
|
||||
async def extract_data(self, list_keys: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
extract json data from either json or form data
|
||||
|
||||
Args:
|
||||
list_keys(Optional[List[str]], optional): optional list of keys which must be forced to list from form data
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: raw json object or form data converted to json
|
||||
"""
|
||||
try:
|
||||
json: Dict[str, Any] = await self.request.json()
|
||||
return json
|
||||
except ValueError:
|
||||
return await self.data_as_json(list_keys or [])
|
||||
|
||||
async def data_as_json(self, list_keys: List[str]) -> Dict[str, Any]:
|
||||
"""
|
||||
extract form data and convert it to json object
|
||||
@ -158,3 +147,39 @@ class BaseView(View):
|
||||
else:
|
||||
json[key] = value
|
||||
return json
|
||||
|
||||
async def extract_data(self, list_keys: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
extract json data from either json or form data
|
||||
|
||||
Args:
|
||||
list_keys(Optional[List[str]], optional): optional list of keys which must be forced to list from form data
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: raw json object or form data converted to json
|
||||
"""
|
||||
try:
|
||||
json: Dict[str, Any] = await self.request.json()
|
||||
return json
|
||||
except ValueError:
|
||||
return await self.data_as_json(list_keys or [])
|
||||
|
||||
# pylint: disable=not-callable,protected-access
|
||||
async def head(self) -> StreamResponse: # type: ignore
|
||||
"""
|
||||
HEAD method implementation based on the result of GET method
|
||||
|
||||
Raises:
|
||||
HTTPMethodNotAllowed: in case if there is no GET method implemented
|
||||
"""
|
||||
get_method: Optional[Callable[[], Awaitable[StreamResponse]]] = getattr(self, "get", None)
|
||||
# using if/else in order to suppress mypy warning which doesn't know that
|
||||
# ``_raise_allowed_methods`` raises exception
|
||||
if get_method is not None:
|
||||
# there is a bug in pylint, see https://github.com/pylint-dev/pylint/issues/6005
|
||||
response = await get_method()
|
||||
response._body = b"" # type: ignore
|
||||
return response
|
||||
|
||||
self._raise_allowed_methods()
|
||||
|
@ -41,10 +41,9 @@ class IndexView(BaseView):
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
HEAD_PERMISSION(UserAccess): (class attribute) head permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Unauthorized
|
||||
GET_PERMISSION = UserAccess.Unauthorized
|
||||
|
||||
@aiohttp_jinja2.template("build-status.jinja2")
|
||||
async def get(self) -> Dict[str, Any]:
|
||||
|
@ -17,9 +17,14 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -33,34 +38,28 @@ class AddView(BaseView):
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Add new package",
|
||||
description="Add new package(s) from AUR",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
add new package
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"packages": ["ahriman"] # either list of packages or package name as in AUR
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/service/add' -d '{"packages": ["ahriman"]}'
|
||||
> POST /api/v1/service/add HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 25
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 18:44:21 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
|
@ -17,9 +17,15 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.pgp_key_id_schema import PGPKeyIdSchema
|
||||
from ahriman.web.schemas.pgp_key_schema import PGPKeySchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -29,17 +35,31 @@ class PGPView(BaseView):
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
HEAD_PERMISSION(UserAccess): (class attribute) head permissions of self
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Reporter
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Search for PGP key",
|
||||
description="Search for PGP key and retrieve its body",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": PGPKeySchema},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(PGPKeyIdSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
retrieve key from the key server. It supports two query parameters: ``key`` - pgp key fingerprint and
|
||||
``server`` which points to valid PGP key server
|
||||
retrieve key from the key server
|
||||
|
||||
Returns:
|
||||
Response: 200 with key body on success
|
||||
@ -47,24 +67,7 @@ class PGPView(BaseView):
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNotFound: if key wasn't found or service was unable to fetch it
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Accept: application/json' 'http://example.com/api/v1/service/pgp?key=0xE989490C&server=keyserver.ubuntu.com'
|
||||
> GET /api/v1/service/pgp?key=0xE989490C&server=keyserver.ubuntu.com HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: application/json
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json; charset=utf-8
|
||||
< Content-Length: 3275
|
||||
< Date: Fri, 25 Nov 2022 22:54:02 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
{"key": "key"}
|
||||
"""
|
||||
"""
|
||||
try:
|
||||
key = self.get_non_empty(self.request.query.getone, "key")
|
||||
server = self.get_non_empty(self.request.query.getone, "server")
|
||||
@ -78,36 +81,28 @@ class PGPView(BaseView):
|
||||
|
||||
return json_response({"key": key})
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Fetch PGP key",
|
||||
description="Fetch PGP key from the key server",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.json_schema(PGPKeyIdSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
store key to the local service environment
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"key": "0x8BE91E5A773FB48AC05CC1EDBED105AED6246B39", # key fingerprint to import
|
||||
"server": "keyserver.ubuntu.com" # optional pgp server address
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/service/pgp' -d '{"key": "0xE989490C"}'
|
||||
> POST /api/v1/service/pgp HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 21
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Fri, 25 Nov 2022 22:55:56 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
data = await self.extract_data()
|
||||
|
||||
|
@ -17,9 +17,14 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -33,40 +38,33 @@ class RebuildView(BaseView):
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Rebuild packages",
|
||||
description="Rebuild packages which depend on specified one",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
rebuild packages based on their dependency
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"packages": ["ahriman"] # either list of packages or package name of dependency
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/service/rebuild' -d '{"packages": ["python"]}'
|
||||
> POST /api/v1/service/rebuild HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 24
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Sun, 27 Nov 2022 00:22:26 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = self.get_non_empty(lambda key: [package for package in data[key] if package], "packages")
|
||||
depends_on = next(package for package in packages)
|
||||
depends_on = next(iter(packages))
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
|
@ -17,9 +17,14 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -33,35 +38,28 @@ class RemoveView(BaseView):
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Remove packages",
|
||||
description="Remove specified packages from the repository",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
remove existing packages
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"packages": ["ahriman"] # either list of packages or package name
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/service/remove' -d '{"packages": ["ahriman"]}'
|
||||
> POST /api/v1/service/remove HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 25
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 18:57:56 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
|
@ -17,9 +17,14 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -33,35 +38,28 @@ class RequestView(BaseView):
|
||||
|
||||
POST_PERMISSION = UserAccess.Reporter
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Request new package",
|
||||
description="Request new package(s) to be added from AUR",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
request to add new package
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"packages": ["ahriman"] # either list of packages or package name as in AUR
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/service/request' -d '{"packages": ["ahriman"]}'
|
||||
> POST /api/v1/service/request HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 25
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 18:59:32 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
|
@ -17,12 +17,18 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
|
||||
from typing import Callable, List
|
||||
|
||||
from ahriman.core.alpm.remote import AUR
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.aur_package_schema import AURPackageSchema
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.search_schema import SearchSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -32,14 +38,29 @@ class SearchView(BaseView):
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
HEAD_PERMISSION(UserAccess): (class attribute) head permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Reporter
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Search for package",
|
||||
description="Search for package in AUR",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": AURPackageSchema(many=True)},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(SearchSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
search packages in AUR. Search string (non-empty) must be supplied as ``for`` parameter
|
||||
search packages in AUR
|
||||
|
||||
Returns:
|
||||
Response: 200 with found package bases and descriptions sorted by base
|
||||
@ -47,23 +68,6 @@ class SearchView(BaseView):
|
||||
Raises:
|
||||
HTTPBadRequest: in case if bad data is supplied
|
||||
HTTPNotFound: if no packages found
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Accept: application/json' 'http://example.com/api/v1/service/search?for=ahriman'
|
||||
> GET /api/v1/service/search?for=ahriman HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: application/json
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json; charset=utf-8
|
||||
< Content-Length: 148
|
||||
< Date: Wed, 23 Nov 2022 19:07:13 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
[{"package": "ahriman", "description": "ArcH linux ReposItory MANager"}, {"package": "ahriman-git", "description": "ArcH Linux ReposItory MANager"}]
|
||||
"""
|
||||
try:
|
||||
search: List[str] = self.get_non_empty(lambda key: self.request.query.getall(key, default=[]), "for")
|
||||
|
@ -17,9 +17,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPNoContent
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -33,26 +37,25 @@ class UpdateView(BaseView):
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
summary="Update packages",
|
||||
description="Run repository update process",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
run repository update. No parameters supported here
|
||||
|
||||
Raises:
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -XPOST 'http://example.com/api/v1/service/update'
|
||||
> POST /api/v1/service/update HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Fri, 25 Nov 2022 22:57:56 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
self.spawner.packages_update()
|
||||
|
||||
|
@ -17,11 +17,18 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
|
||||
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.log_schema import LogSchema
|
||||
from ahriman.web.schemas.logs_schema import LogsSchema
|
||||
from ahriman.web.schemas.package_name_schema import PackageNameSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -32,39 +39,53 @@ class LogsView(BaseView):
|
||||
Attributes:
|
||||
DELETE_PERMISSION(UserAccess): (class attribute) delete permissions of self
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
HEAD_PERMISSION(UserAccess): (class attribute) head permissions of self
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full
|
||||
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Reporter
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Delete package logs",
|
||||
description="Delete all logs which belong to the specified package",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [DELETE_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
async def delete(self) -> None:
|
||||
"""
|
||||
delete package logs
|
||||
|
||||
Raises:
|
||||
HTTPNoContent: on success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -XDELETE 'http://example.com/api/v1/packages/ahriman/logs'
|
||||
> DELETE /api/v1/packages/ahriman/logs HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 19:26:40 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
self.service.remove_logs(package_base, None)
|
||||
|
||||
raise HTTPNoContent()
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Get package logs",
|
||||
description="Retrieve all package logs and the last package status",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": LogsSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get last package logs
|
||||
@ -72,22 +93,8 @@ class LogsView(BaseView):
|
||||
Returns:
|
||||
Response: 200 with package logs on success
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Accept: application/json' 'http://example.com/api/v1/packages/ahriman/logs'
|
||||
> GET /api/v1/packages/ahriman/logs HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: application/json
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json; charset=utf-8
|
||||
< Content-Length: 100112
|
||||
< Date: Wed, 23 Nov 2022 19:24:14 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
{"package_base": "ahriman", "status": {"status": "success", "timestamp": 1669231136}, "logs": "[2022-11-23 19:17:32] clone remote https://aur.archlinux.org/ahriman.git to /tmp/tmpy9j6fq9p using branch master"}
|
||||
Raises:
|
||||
HTTPNotFound: if package base is unknown
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
|
||||
@ -104,37 +111,29 @@ class LogsView(BaseView):
|
||||
}
|
||||
return json_response(response)
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Add package logs",
|
||||
description="Insert new package log record",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
@aiohttp_apispec.json_schema(LogSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
create new package log record
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"created": 42.001, # log record created timestamp
|
||||
"message": "log message", # log record
|
||||
"process_id": 42 # process id from which log record was emitted
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/packages/ahriman/logs' -d '{"created": 1669231764.042444, "message": "my log message", "process_id": 1}'
|
||||
> POST /api/v1/packages/ahriman/logs HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 76
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 19:30:45 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
data = await self.extract_data()
|
||||
|
@ -17,12 +17,18 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
|
||||
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.package_name_schema import PackageNameSchema
|
||||
from ahriman.web.schemas.package_status_schema import PackageStatusSchema, PackageStatusSimplifiedSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -33,39 +39,53 @@ class PackageView(BaseView):
|
||||
Attributes:
|
||||
DELETE_PERMISSION(UserAccess): (class attribute) delete permissions of self
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
HEAD_PERMISSION(UserAccess): (class attribute) head permissions of self
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full
|
||||
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||
GET_PERMISSION = UserAccess.Read
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Delete package",
|
||||
description="Delete package and its status from service",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [DELETE_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
async def delete(self) -> None:
|
||||
"""
|
||||
delete package base from status page
|
||||
|
||||
Raises:
|
||||
HTTPNoContent: on success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -XDELETE 'http://example.com/api/v1/packages/ahriman'
|
||||
> DELETE /api/v1/packages/ahriman HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 19:43:40 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
self.service.remove(package_base)
|
||||
|
||||
raise HTTPNoContent()
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Get package",
|
||||
description="Retrieve packages and its descriptor",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": PackageStatusSchema(many=True)},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get current package base status
|
||||
@ -75,23 +95,6 @@ class PackageView(BaseView):
|
||||
|
||||
Raises:
|
||||
HTTPNotFound: if no package was found
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Accept: application/json' 'http://example.com/api/v1/packages/ahriman'
|
||||
> GET /api/v1/packages/ahriman HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: application/json
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json; charset=utf-8
|
||||
< Content-Length: 743
|
||||
< Date: Wed, 23 Nov 2022 19:41:01 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
[{"package": {"base": "ahriman", "version": "2.3.0-1", "remote": {"git_url": "https://aur.archlinux.org/ahriman.git", "web_url": "https://aur.archlinux.org/packages/ahriman", "path": ".", "branch": "master", "source": "aur"}, "packages": {"ahriman": {"architecture": "any", "archive_size": 247573, "build_date": 1669231069, "depends": ["devtools", "git", "pyalpm", "python-inflection", "python-passlib", "python-requests", "python-setuptools", "python-srcinfo"], "description": "ArcH linux ReposItory MANager", "filename": "ahriman-2.3.0-1-any.pkg.tar.zst", "groups": [], "installed_size": 1676153, "licenses": ["GPL3"], "provides": [], "url": "https://github.com/arcan1s/ahriman"}}}, "status": {"status": "success", "timestamp": 1669231136}}]
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
|
||||
@ -108,37 +111,29 @@ class PackageView(BaseView):
|
||||
]
|
||||
return json_response(response)
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Update package",
|
||||
description="Update package status and set its descriptior optionally",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
@aiohttp_apispec.json_schema(PackageStatusSimplifiedSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
update package build status
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"status": "unknown", # package build status string, must be valid ``BuildStatusEnum``
|
||||
"package": {} # package body (use ``dataclasses.asdict`` to generate one), optional.
|
||||
# Must be supplied in case if package base is unknown
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/packages/ahriman' -d '{"status": "success"}'
|
||||
> POST /api/v1/packages/ahriman HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 21
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 19:42:49 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
data = await self.extract_data()
|
||||
|
@ -17,9 +17,14 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPNoContent, Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.package_status_schema import PackageStatusSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -29,36 +34,31 @@ class PackagesView(BaseView):
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
HEAD_PERMISSION(UserAccess): (class attribute) head permissions of self
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||
GET_PERMISSION = UserAccess.Read
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Get packages list",
|
||||
description="Retrieve all packages and their descriptors",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": PackageStatusSchema(many=True)},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get current packages status
|
||||
|
||||
Returns:
|
||||
Response: 200 with package description on success
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Accept: application/json' 'http://example.com/api/v1/packages'
|
||||
> GET /api/v1/packages HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: application/json
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json; charset=utf-8
|
||||
< Content-Length: 2687
|
||||
< Date: Wed, 23 Nov 2022 19:35:24 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
[{"package": {"base": "ahriman", "version": "2.3.0-1", "remote": {"git_url": "https://aur.archlinux.org/ahriman.git", "web_url": "https://aur.archlinux.org/packages/ahriman", "path": ".", "branch": "master", "source": "aur"}, "packages": {"ahriman": {"architecture": "any", "archive_size": 247573, "build_date": 1669231069, "depends": ["devtools", "git", "pyalpm", "python-inflection", "python-passlib", "python-requests", "python-setuptools", "python-srcinfo"], "description": "ArcH linux ReposItory MANager", "filename": "ahriman-2.3.0-1-any.pkg.tar.zst", "groups": [], "installed_size": 1676153, "licenses": ["GPL3"], "provides": [], "url": "https://github.com/arcan1s/ahriman"}}}, "status": {"status": "success", "timestamp": 1669231136}}]
|
||||
"""
|
||||
response = [
|
||||
{
|
||||
@ -68,26 +68,25 @@ class PackagesView(BaseView):
|
||||
]
|
||||
return json_response(response)
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
summary="Load packages",
|
||||
description="Load packages from cache",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
reload all packages from repository. No parameters supported here
|
||||
reload all packages from repository
|
||||
|
||||
Raises:
|
||||
HTTPNoContent: on success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -XPOST 'http://example.com/api/v1/packages'
|
||||
> POST /api/v1/packages HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 19:38:06 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
self.service.load()
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
|
||||
|
||||
from ahriman import version
|
||||
@ -24,6 +26,10 @@ from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.counters import Counters
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.internal_status_schema import InternalStatusSchema
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -33,36 +39,31 @@ class StatusView(BaseView):
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
HEAD_PERMISSION(UserAccess): (class attribute) head permissions of self
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = HEAD_PERMISSION = UserAccess.Read
|
||||
GET_PERMISSION = UserAccess.Read
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Status"],
|
||||
summary="Web service status",
|
||||
description="Get web service status counters",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": InternalStatusSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get current service status
|
||||
|
||||
Returns:
|
||||
Response: 200 with service status object
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Accept: application/json' 'http://example.com/api/v1/status'
|
||||
> GET /api/v1/status HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: application/json
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json; charset=utf-8
|
||||
< Content-Length: 222
|
||||
< Date: Wed, 23 Nov 2022 19:32:31 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
{"status": {"status": "success", "timestamp": 1669231237}, "architecture": "x86_64", "packages": {"total": 4, "unknown": 0, "pending": 0, "building": 0, "failed": 0, "success": 4}, "repository": "repo", "version": "2.3.0"}
|
||||
"""
|
||||
counters = Counters.from_packages(self.service.packages)
|
||||
status = InternalStatus(
|
||||
@ -74,35 +75,28 @@ class StatusView(BaseView):
|
||||
|
||||
return json_response(status.view())
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Status"],
|
||||
summary="Set web service status",
|
||||
description="Update web service status. Counters will remain unchanged",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.json_schema(StatusSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
update service status
|
||||
|
||||
JSON body must be supplied, the following model is used::
|
||||
|
||||
{
|
||||
"status": "unknown", # service status string, must be valid ``BuildStatusEnum``
|
||||
}
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/status' -d '{"status": "success"}'
|
||||
> POST /api/v1/status HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 21
|
||||
>
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Wed, 23 Nov 2022 19:33:57 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
"""
|
||||
try:
|
||||
data = await self.extract_data()
|
||||
|
@ -17,10 +17,15 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized
|
||||
|
||||
from ahriman.core.auth.helpers import remember
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.login_schema import LoginSchema
|
||||
from ahriman.web.schemas.oauth2_schema import OAuth2Schema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -35,6 +40,18 @@ class LoginView(BaseView):
|
||||
|
||||
GET_PERMISSION = POST_PERMISSION = UserAccess.Unauthorized
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Login"],
|
||||
summary="Login via OAuth2",
|
||||
description="Login by using OAuth2 authorization code. Only available if OAuth2 is enabled",
|
||||
responses={
|
||||
302: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.querystring_schema(OAuth2Schema)
|
||||
async def get(self) -> None:
|
||||
"""
|
||||
OAuth2 response handler
|
||||
@ -48,9 +65,6 @@ class LoginView(BaseView):
|
||||
HTTPFound: on success response
|
||||
HTTPMethodNotAllowed: in case if method is used, but OAuth is disabled
|
||||
HTTPUnauthorized: if case of authorization error
|
||||
|
||||
Examples:
|
||||
This request must not be used directly.
|
||||
"""
|
||||
from ahriman.core.auth.oauth import OAuth
|
||||
|
||||
@ -70,43 +84,25 @@ class LoginView(BaseView):
|
||||
|
||||
raise HTTPUnauthorized()
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Login"],
|
||||
summary="Login via basic authorization",
|
||||
description="Login by using username and password",
|
||||
responses={
|
||||
302: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.json_schema(LoginSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
login user to service
|
||||
|
||||
either JSON body or form data must be supplied the following fields are required::
|
||||
|
||||
{
|
||||
"username": "username", # username to use for login
|
||||
"password": "pa55w0rd" # password to use for login
|
||||
}
|
||||
|
||||
The authentication session will be passed in ``Set-Cookie`` header.
|
||||
login user to service. The authentication session will be passed in ``Set-Cookie`` header.
|
||||
|
||||
Raises:
|
||||
HTTPFound: on success response
|
||||
HTTPUnauthorized: if case of authorization error
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -H 'Content-Type: application/json' 'http://example.com/api/v1/login' -d '{"username": "test", "password": "test"}'
|
||||
> POST /api/v1/login HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 40
|
||||
>
|
||||
< HTTP/1.1 302 Found
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
< Location: /
|
||||
< Content-Length: 10
|
||||
< Set-Cookie: ...
|
||||
< Date: Wed, 23 Nov 2022 17:51:27 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
302: Found
|
||||
"""
|
||||
data = await self.extract_data()
|
||||
identity = data.get("username")
|
||||
|
@ -17,10 +17,14 @@
|
||||
# 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 aiohttp_apispec # type: ignore
|
||||
|
||||
from aiohttp.web import HTTPFound, HTTPUnauthorized
|
||||
|
||||
from ahriman.core.auth.helpers import check_authorized, forget
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -34,33 +38,26 @@ class LogoutView(BaseView):
|
||||
|
||||
POST_PERMISSION = UserAccess.Unauthorized
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Login"],
|
||||
summary="Logout",
|
||||
description="Logout user and remove authorization cookies",
|
||||
responses={
|
||||
302: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
logout user from the service. No parameters supported here.
|
||||
logout user from the service
|
||||
|
||||
The server will respond with ``Set-Cookie`` header, in which API session cookie will be nullified.
|
||||
|
||||
Raises:
|
||||
HTTPFound: on success response
|
||||
|
||||
Examples:
|
||||
Example of command by using curl::
|
||||
|
||||
$ curl -v -XPOST 'http://example.com/api/v1/logout'
|
||||
> POST /api/v1/logout HTTP/1.1
|
||||
> Host: example.com
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 302 Found
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
< Location: /
|
||||
< Content-Length: 10
|
||||
< Set-Cookie: ...
|
||||
< Date: Wed, 23 Nov 2022 19:10:51 GMT
|
||||
< Server: Python/3.10 aiohttp/3.8.3
|
||||
<
|
||||
302: Found
|
||||
"""
|
||||
try:
|
||||
await check_authorized(self.request)
|
||||
|
@ -22,7 +22,7 @@ import jinja2
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.web import Application, normalize_path_middleware, run_app
|
||||
from typing import Optional
|
||||
|
||||
from ahriman.core.auth import Auth
|
||||
@ -32,20 +32,22 @@ from ahriman.core.exceptions import InitializeError
|
||||
from ahriman.core.log.filtered_access_logger import FilteredAccessLogger
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.web.apispec import setup_apispec
|
||||
from ahriman.web.cors import setup_cors
|
||||
from ahriman.web.middlewares.exception_handler import exception_handler
|
||||
from ahriman.web.routes import setup_routes
|
||||
|
||||
|
||||
__all__ = ["create_socket", "on_shutdown", "on_startup", "run_server", "setup_service"]
|
||||
__all__ = ["run_server", "setup_service"]
|
||||
|
||||
|
||||
def create_socket(configuration: Configuration, application: web.Application) -> Optional[socket.socket]:
|
||||
def _create_socket(configuration: Configuration, application: Application) -> Optional[socket.socket]:
|
||||
"""
|
||||
create unix socket based on configuration option
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
application(web.Application): web application instance
|
||||
application(Application): web application instance
|
||||
|
||||
Returns:
|
||||
Optional[socket.socket]: unix socket object if set by option
|
||||
@ -64,7 +66,7 @@ def create_socket(configuration: Configuration, application: web.Application) ->
|
||||
unix_socket.chmod(0o666) # for the glory of satan of course
|
||||
|
||||
# register socket removal
|
||||
async def remove_socket(_: web.Application) -> None:
|
||||
async def remove_socket(_: Application) -> None:
|
||||
unix_socket.unlink(missing_ok=True)
|
||||
|
||||
application.on_shutdown.append(remove_socket)
|
||||
@ -72,22 +74,22 @@ def create_socket(configuration: Configuration, application: web.Application) ->
|
||||
return sock
|
||||
|
||||
|
||||
async def on_shutdown(application: web.Application) -> None:
|
||||
async def _on_shutdown(application: Application) -> None:
|
||||
"""
|
||||
web application shutdown handler
|
||||
|
||||
Args:
|
||||
application(web.Application): web application instance
|
||||
application(Application): web application instance
|
||||
"""
|
||||
application.logger.warning("server terminated")
|
||||
|
||||
|
||||
async def on_startup(application: web.Application) -> None:
|
||||
async def _on_startup(application: Application) -> None:
|
||||
"""
|
||||
web application start handler
|
||||
|
||||
Args:
|
||||
application(web.Application): web application instance
|
||||
application(Application): web application instance
|
||||
|
||||
Raises:
|
||||
InitializeError: in case if matched could not be loaded
|
||||
@ -101,25 +103,25 @@ async def on_startup(application: web.Application) -> None:
|
||||
raise InitializeError(message)
|
||||
|
||||
|
||||
def run_server(application: web.Application) -> None:
|
||||
def run_server(application: Application) -> None:
|
||||
"""
|
||||
run web application
|
||||
|
||||
Args:
|
||||
application(web.Application): web application instance
|
||||
application(Application): web application instance
|
||||
"""
|
||||
application.logger.info("start server")
|
||||
|
||||
configuration: Configuration = application["configuration"]
|
||||
host = configuration.get("web", "host")
|
||||
port = configuration.getint("web", "port")
|
||||
unix_socket = create_socket(configuration, application)
|
||||
unix_socket = _create_socket(configuration, application)
|
||||
|
||||
web.run_app(application, host=host, port=port, sock=unix_socket, handle_signals=True,
|
||||
access_log=logging.getLogger("http"), access_log_class=FilteredAccessLogger)
|
||||
run_app(application, host=host, port=port, sock=unix_socket, handle_signals=True,
|
||||
access_log=logging.getLogger("http"), access_log_class=FilteredAccessLogger)
|
||||
|
||||
|
||||
def setup_service(architecture: str, configuration: Configuration, spawner: Spawn) -> web.Application:
|
||||
def setup_service(architecture: str, configuration: Configuration, spawner: Spawn) -> Application:
|
||||
"""
|
||||
create web application
|
||||
|
||||
@ -129,18 +131,21 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw
|
||||
spawner(Spawn): spawner thread
|
||||
|
||||
Returns:
|
||||
web.Application: web application instance
|
||||
Application: web application instance
|
||||
"""
|
||||
application = web.Application(logger=logging.getLogger(__name__))
|
||||
application.on_shutdown.append(on_shutdown)
|
||||
application.on_startup.append(on_startup)
|
||||
application = Application(logger=logging.getLogger(__name__))
|
||||
application.on_shutdown.append(_on_shutdown)
|
||||
application.on_startup.append(_on_startup)
|
||||
|
||||
application.middlewares.append(web.normalize_path_middleware(append_slash=False, remove_slash=True))
|
||||
application.middlewares.append(normalize_path_middleware(append_slash=False, remove_slash=True))
|
||||
application.middlewares.append(exception_handler(application.logger))
|
||||
|
||||
application.logger.info("setup routes")
|
||||
setup_routes(application, configuration.getpath("web", "static_path"))
|
||||
|
||||
application.logger.info("setup CORS")
|
||||
setup_cors(application)
|
||||
|
||||
application.logger.info("setup templates")
|
||||
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(configuration.getpath("web", "templates")))
|
||||
|
||||
@ -170,4 +175,7 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw
|
||||
from ahriman.web.middlewares.auth_handler import setup_auth
|
||||
setup_auth(application, configuration, validator)
|
||||
|
||||
application.logger.info("setup api docs")
|
||||
setup_apispec(application)
|
||||
|
||||
return application
|
||||
|
Reference in New Issue
Block a user