mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-15 06:55:48 +00:00
Add ability to trigger updates from the web (#31)
* add external process spawner and update test cases * pass no_report to handlers * provide service api endpoints * do not spawn process for single architecture run * pass no report to handlers * make _call method of handlers public and also simplify process spawn * move update under add * implement actions from web page * clear logging & improve l&f
This commit is contained in:
@ -27,11 +27,10 @@ from ahriman import version
|
||||
from ahriman.application import handlers
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.sign_settings import SignSettings
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
# pylint thinks it is bad idea, but get the fuck off
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
SubParserAction = argparse._SubParsersAction # pylint: disable=protected-access
|
||||
|
||||
|
||||
@ -367,7 +366,7 @@ def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
parser = root.add_parser("web", help="start web server", description="start web server",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.set_defaults(handler=handlers.Web, lock=None, no_report=True)
|
||||
parser.set_defaults(handler=handlers.Web, lock=None, no_report=True, parser=_parser)
|
||||
return parser
|
||||
|
||||
|
||||
|
@ -40,16 +40,17 @@ class Application:
|
||||
:ivar repository: repository instance
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
def __init__(self, architecture: str, configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
self.logger = logging.getLogger("root")
|
||||
self.configuration = configuration
|
||||
self.architecture = architecture
|
||||
self.repository = Repository(architecture, configuration)
|
||||
self.repository = Repository(architecture, configuration, no_report)
|
||||
|
||||
def _finalize(self, built_packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
|
@ -32,14 +32,16 @@ class Add(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
application = Application(architecture, configuration)
|
||||
application = Application(architecture, configuration, no_report)
|
||||
application.add(args.package, args.without_dependencies)
|
||||
if not args.now:
|
||||
return
|
||||
|
@ -32,12 +32,14 @@ class Clean(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration).clean(args.no_build, args.no_cache, args.no_chroot,
|
||||
args.no_manual, args.no_packages)
|
||||
Application(architecture, configuration, no_report).clean(args.no_build, args.no_cache, args.no_chroot,
|
||||
args.no_manual, args.no_packages)
|
||||
|
@ -34,12 +34,14 @@ class CreateUser(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
salt = CreateUser.get_salt(configuration)
|
||||
user = CreateUser.create_user(args)
|
||||
|
@ -33,12 +33,14 @@ class Dump(Handler):
|
||||
_print = print
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
dump = configuration.dump()
|
||||
for section, values in sorted(dump.items()):
|
||||
|
@ -27,17 +27,20 @@ from typing import Set, Type
|
||||
|
||||
from ahriman.application.lock import Lock
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import MissingArchitecture
|
||||
from ahriman.core.exceptions import MissingArchitecture, MultipleArchitecture
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
class Handler:
|
||||
"""
|
||||
base handler class for command callbacks
|
||||
:cvar ALLOW_MULTI_ARCHITECTURE_RUN: allow to run with multiple architectures
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = True
|
||||
|
||||
@classmethod
|
||||
def _call(cls: Type[Handler], args: argparse.Namespace, architecture: str) -> bool:
|
||||
def call(cls: Type[Handler], args: argparse.Namespace, architecture: str) -> bool:
|
||||
"""
|
||||
additional function to wrap all calls for multiprocessing library
|
||||
:param args: command line args
|
||||
@ -47,7 +50,7 @@ class Handler:
|
||||
try:
|
||||
configuration = Configuration.from_path(args.configuration, architecture, not args.no_log)
|
||||
with Lock(args, architecture, configuration):
|
||||
cls.run(args, architecture, configuration)
|
||||
cls.run(args, architecture, configuration, args.no_report)
|
||||
return True
|
||||
except Exception:
|
||||
logging.getLogger("root").exception("process exception")
|
||||
@ -61,9 +64,18 @@ class Handler:
|
||||
:return: 0 on success, 1 otherwise
|
||||
"""
|
||||
architectures = cls.extract_architectures(args)
|
||||
with Pool(len(architectures)) as pool:
|
||||
result = pool.starmap(
|
||||
cls._call, [(args, architecture) for architecture in architectures])
|
||||
|
||||
# actually we do not have to spawn another process if it is single-process application, do we?
|
||||
if len(architectures) > 1:
|
||||
if not cls.ALLOW_MULTI_ARCHITECTURE_RUN:
|
||||
raise MultipleArchitecture(args.command)
|
||||
|
||||
with Pool(len(architectures)) as pool:
|
||||
result = pool.starmap(
|
||||
cls.call, [(args, architecture) for architecture in architectures])
|
||||
else:
|
||||
result = [cls.call(args, architectures.pop())]
|
||||
|
||||
return 0 if all(result) else 1
|
||||
|
||||
@classmethod
|
||||
@ -88,11 +100,13 @@ class Handler:
|
||||
return architectures
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
@ -32,11 +32,13 @@ class Init(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration).repository.repo.init()
|
||||
Application(architecture, configuration, no_report).repository.repo.init()
|
||||
|
@ -32,11 +32,13 @@ class KeyImport(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration).repository.sign.import_key(args.key_server, args.key)
|
||||
Application(architecture, configuration, no_report).repository.sign.import_key(args.key_server, args.key)
|
||||
|
@ -32,16 +32,18 @@ class Rebuild(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
depends_on = set(args.depends_on) if args.depends_on else None
|
||||
|
||||
application = Application(architecture, configuration)
|
||||
application = Application(architecture, configuration, no_report)
|
||||
packages = [
|
||||
package
|
||||
for package in application.repository.packages()
|
||||
|
@ -32,11 +32,13 @@ class Remove(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration).remove(args.package)
|
||||
Application(architecture, configuration, no_report).remove(args.package)
|
||||
|
@ -33,14 +33,16 @@ class RemoveUnknown(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
application = Application(architecture, configuration)
|
||||
application = Application(architecture, configuration, no_report)
|
||||
unknown_packages = application.unknown()
|
||||
if args.dry_run:
|
||||
for package in unknown_packages:
|
||||
|
@ -32,11 +32,13 @@ class Report(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration).report(args.target, [])
|
||||
Application(architecture, configuration, no_report).report(args.target, [])
|
||||
|
@ -32,12 +32,14 @@ class Search(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
search = " ".join(args.search)
|
||||
packages = aur.search(search)
|
||||
|
@ -43,14 +43,16 @@ class Setup(Handler):
|
||||
SUDOERS_PATH = Path("/etc/sudoers.d/ahriman")
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
application = Application(architecture, configuration)
|
||||
application = Application(architecture, configuration, no_report)
|
||||
Setup.create_makepkg_configuration(args.packager, application.repository.paths)
|
||||
Setup.create_executable(args.build_command, architecture)
|
||||
Setup.create_devtools_configuration(args.build_command, architecture, args.from_configuration,
|
||||
|
@ -32,11 +32,13 @@ class Sign(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration).sign(args.package)
|
||||
Application(architecture, configuration, no_report).sign(args.package)
|
||||
|
@ -34,24 +34,27 @@ class Status(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
application = Application(architecture, configuration)
|
||||
# we are using reporter here
|
||||
client = Application(architecture, configuration, no_report=False).repository.reporter
|
||||
if args.ahriman:
|
||||
ahriman = application.repository.reporter.get_self()
|
||||
ahriman = client.get_self()
|
||||
print(ahriman.pretty_print())
|
||||
print()
|
||||
if args.package:
|
||||
packages: Iterable[Tuple[Package, BuildStatus]] = sum(
|
||||
[application.repository.reporter.get(base) for base in args.package],
|
||||
[client.get(base) for base in args.package],
|
||||
start=[])
|
||||
else:
|
||||
packages = application.repository.reporter.get(None)
|
||||
packages = client.get(None)
|
||||
for package, package_status in sorted(packages, key=lambda item: item[0].base):
|
||||
print(package.pretty_print())
|
||||
print(f"\t{package.version}")
|
||||
|
@ -32,14 +32,17 @@ class StatusUpdate(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
client = Application(architecture, configuration).repository.reporter
|
||||
# we are using reporter here
|
||||
client = Application(architecture, configuration, no_report=False).repository.reporter
|
||||
callback: Callable[[str], None] = lambda p: client.remove(p) if args.remove else client.update(p, args.status)
|
||||
if args.package:
|
||||
# update packages statuses
|
||||
|
@ -32,11 +32,13 @@ class Sync(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration).sync(args.target, [])
|
||||
Application(architecture, configuration, no_report).sync(args.target, [])
|
||||
|
@ -32,14 +32,16 @@ class Update(Handler):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
application = Application(architecture, configuration)
|
||||
application = Application(architecture, configuration, no_report)
|
||||
packages = application.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs,
|
||||
Update.log_fn(application, args.dry_run))
|
||||
if args.dry_run:
|
||||
|
@ -23,6 +23,7 @@ from typing import Type
|
||||
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.spawn import Spawn
|
||||
|
||||
|
||||
class Web(Handler):
|
||||
@ -30,14 +31,23 @@ class Web(Handler):
|
||||
web server handler
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
:param args: command line args
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
# we are using local import for optional dependencies
|
||||
from ahriman.web.web import run_server, setup_service
|
||||
application = setup_service(architecture, configuration)
|
||||
|
||||
spawner = Spawn(args.parser(), architecture, configuration)
|
||||
spawner.start()
|
||||
|
||||
application = setup_service(architecture, configuration, spawner)
|
||||
run_server(application)
|
||||
|
@ -19,7 +19,7 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Set, Type
|
||||
from typing import Optional, Type
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.auth_settings import AuthSettings
|
||||
@ -36,8 +36,8 @@ class Auth:
|
||||
:cvar ALLOWED_PATHS_GROUPS: URI paths prefixes which can be accessed without authorization, predefined
|
||||
"""
|
||||
|
||||
ALLOWED_PATHS = {"/", "/favicon.ico", "/index.html", "/login", "/logout"}
|
||||
ALLOWED_PATHS_GROUPS: Set[str] = set()
|
||||
ALLOWED_PATHS = {"/", "/favicon.ico", "/index.html"}
|
||||
ALLOWED_PATHS_GROUPS = {"/user-api"}
|
||||
|
||||
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
|
||||
"""
|
||||
|
@ -109,6 +109,19 @@ class MissingArchitecture(Exception):
|
||||
Exception.__init__(self, f"Architecture required for subcommand {command}, but missing")
|
||||
|
||||
|
||||
class MultipleArchitecture(Exception):
|
||||
"""
|
||||
exception which will be raised if multiple architectures are not supported by the handler
|
||||
"""
|
||||
|
||||
def __init__(self, command: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param command: command name which throws exception
|
||||
"""
|
||||
Exception.__init__(self, f"Multiple architectures are not supported by subcommand {command}")
|
||||
|
||||
|
||||
class ReportFailed(Exception):
|
||||
"""
|
||||
report generation exception
|
||||
|
@ -43,7 +43,13 @@ class Properties:
|
||||
:ivar sign: GPG wrapper instance
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
def __init__(self, architecture: str, configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
self.logger = logging.getLogger("builder")
|
||||
self.architecture = architecture
|
||||
self.configuration = configuration
|
||||
@ -58,4 +64,4 @@ class Properties:
|
||||
self.pacman = Pacman(configuration)
|
||||
self.sign = GPG(architecture, configuration)
|
||||
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
|
||||
self.reporter = Client.load(configuration)
|
||||
self.reporter = Client() if no_report else Client.load(configuration)
|
||||
|
137
src/ahriman/core/spawn.py
Normal file
137
src/ahriman/core/spawn.py
Normal file
@ -0,0 +1,137 @@
|
||||
#
|
||||
# Copyright (c) 2021 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from multiprocessing import Process, Queue
|
||||
from threading import Lock, Thread
|
||||
from typing import Callable, Dict, Iterable, Tuple
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
class Spawn(Thread):
|
||||
"""
|
||||
helper to spawn external ahriman process
|
||||
MUST NOT be used directly, the only one usage allowed is to spawn process from web services
|
||||
:ivar active: map of active child processes required to avoid zombies
|
||||
:ivar architecture: repository architecture
|
||||
:ivar configuration: configuration instance
|
||||
:ivar logger: spawner logger
|
||||
:ivar queue: multiprocessing queue to read updates from processes
|
||||
"""
|
||||
|
||||
def __init__(self, args_parser: argparse.ArgumentParser, architecture: str, configuration: Configuration) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param args_parser: command line parser for the application
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
"""
|
||||
Thread.__init__(self, name="spawn")
|
||||
self.architecture = architecture
|
||||
self.args_parser = args_parser
|
||||
self.configuration = configuration
|
||||
self.logger = logging.getLogger("http")
|
||||
|
||||
self.lock = Lock()
|
||||
self.active: Dict[str, Process] = {}
|
||||
# stupid pylint does not know that it is possible
|
||||
self.queue: Queue[Tuple[str, bool]] = Queue() # pylint: disable=unsubscriptable-object
|
||||
|
||||
@staticmethod
|
||||
def process(callback: Callable[[argparse.Namespace, str], bool], args: argparse.Namespace, architecture: str,
|
||||
process_id: str, queue: Queue[Tuple[str, bool]]) -> None: # pylint: disable=unsubscriptable-object
|
||||
"""
|
||||
helper to run external process
|
||||
:param callback: application run function (i.e. Handler.run method)
|
||||
:param args: command line arguments
|
||||
:param architecture: repository architecture
|
||||
:param process_id: process unique identifier
|
||||
:param queue: output queue
|
||||
"""
|
||||
result = callback(args, architecture)
|
||||
queue.put((process_id, result))
|
||||
|
||||
def packages_add(self, packages: Iterable[str], now: bool) -> None:
|
||||
"""
|
||||
add packages
|
||||
:param packages: packages list to add
|
||||
:param now: build packages now
|
||||
"""
|
||||
kwargs = {"now": ""} if now else {}
|
||||
self.spawn_process("add", *packages, **kwargs)
|
||||
|
||||
def packages_remove(self, packages: Iterable[str]) -> None:
|
||||
"""
|
||||
remove packages
|
||||
:param packages: packages list to remove
|
||||
"""
|
||||
self.spawn_process("remove", *packages)
|
||||
|
||||
def spawn_process(self, command: str, *args: str, **kwargs: str) -> None:
|
||||
"""
|
||||
spawn external ahriman process with supplied arguments
|
||||
:param command: subcommand to run
|
||||
:param args: positional command arguments
|
||||
:param kwargs: named command arguments
|
||||
"""
|
||||
# default arguments
|
||||
arguments = ["--architecture", self.architecture]
|
||||
if self.configuration.path is not None:
|
||||
arguments.extend(["--configuration", str(self.configuration.path)])
|
||||
# positional command arguments
|
||||
arguments.append(command)
|
||||
arguments.extend(args)
|
||||
# named command arguments
|
||||
for argument, value in kwargs.items():
|
||||
arguments.append(f"--{argument}")
|
||||
if value:
|
||||
arguments.append(value)
|
||||
|
||||
process_id = str(uuid.uuid4())
|
||||
self.logger.info("full command line arguments of %s are %s", process_id, arguments)
|
||||
parsed = self.args_parser.parse_args(arguments)
|
||||
|
||||
callback = parsed.handler.call
|
||||
process = Process(target=self.process,
|
||||
args=(callback, parsed, self.architecture, process_id, self.queue),
|
||||
daemon=True)
|
||||
process.start()
|
||||
|
||||
with self.lock:
|
||||
self.active[process_id] = process
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
thread run method
|
||||
"""
|
||||
for process_id, status in iter(self.queue.get, None):
|
||||
self.logger.info("process %s has been terminated with status %s", process_id, status)
|
||||
|
||||
with self.lock:
|
||||
process = self.active.pop(process_id, None)
|
||||
|
||||
if process is not None:
|
||||
process.terminate() # make sure lol
|
||||
process.join()
|
@ -49,7 +49,7 @@ class Watcher:
|
||||
self.logger = logging.getLogger("http")
|
||||
|
||||
self.architecture = architecture
|
||||
self.repository = Repository(architecture, configuration)
|
||||
self.repository = Repository(architecture, configuration, no_report=True)
|
||||
|
||||
self.known: Dict[str, Tuple[Package, BuildStatus]] = {}
|
||||
self.status = BuildStatus()
|
||||
|
@ -65,7 +65,7 @@ class WebClient(Client):
|
||||
"""
|
||||
:return: full url for web service to login
|
||||
"""
|
||||
return f"{self.address}/login"
|
||||
return f"{self.address}/user-api/v1/login"
|
||||
|
||||
@property
|
||||
def _status_url(self) -> str:
|
||||
|
@ -51,7 +51,7 @@ class User:
|
||||
"""
|
||||
if username is None or password is None:
|
||||
return None
|
||||
return cls(username, password, UserAccess.Status)
|
||||
return cls(username, password, UserAccess.Read)
|
||||
|
||||
@staticmethod
|
||||
def generate_password(length: int) -> str:
|
||||
|
@ -25,9 +25,7 @@ class UserAccess(Enum):
|
||||
web user access enumeration
|
||||
:cvar Read: user can read status page
|
||||
:cvar Write: user can modify task and package list
|
||||
:cvar Status: user can update statuses via API
|
||||
"""
|
||||
|
||||
Read = "read"
|
||||
Write = "write"
|
||||
Status = "status"
|
||||
|
@ -73,9 +73,7 @@ def auth_handler(validator: Auth) -> MiddlewareType:
|
||||
"""
|
||||
@middleware
|
||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
||||
if request.path.startswith("/status-api"):
|
||||
permission = UserAccess.Status
|
||||
elif request.method in ("GET", "HEAD", "OPTIONS"):
|
||||
if request.method in ("GET", "HEAD", "OPTIONS"):
|
||||
permission = UserAccess.Read
|
||||
else:
|
||||
permission = UserAccess.Write
|
||||
|
@ -19,13 +19,16 @@
|
||||
#
|
||||
from aiohttp.web import Application
|
||||
|
||||
from ahriman.web.views.ahriman import AhrimanView
|
||||
from ahriman.web.views.index import IndexView
|
||||
from ahriman.web.views.login import LoginView
|
||||
from ahriman.web.views.logout import LogoutView
|
||||
from ahriman.web.views.package import PackageView
|
||||
from ahriman.web.views.packages import PackagesView
|
||||
from ahriman.web.views.status import StatusView
|
||||
from ahriman.web.views.service.add import AddView
|
||||
from ahriman.web.views.service.remove import RemoveView
|
||||
from ahriman.web.views.service.search import SearchView
|
||||
from ahriman.web.views.status.ahriman import AhrimanView
|
||||
from ahriman.web.views.status.package import PackageView
|
||||
from ahriman.web.views.status.packages import PackagesView
|
||||
from ahriman.web.views.status.status import StatusView
|
||||
from ahriman.web.views.user.login import LoginView
|
||||
from ahriman.web.views.user.logout import LogoutView
|
||||
|
||||
|
||||
def setup_routes(application: Application) -> None:
|
||||
@ -37,8 +40,13 @@ def setup_routes(application: Application) -> None:
|
||||
GET / get build status page
|
||||
GET /index.html same as above
|
||||
|
||||
POST /login login to service
|
||||
POST /logout logout from service
|
||||
POST /service-api/v1/add add new packages to repository
|
||||
|
||||
POST /service-api/v1/remove remove existing package from repository
|
||||
|
||||
POST /service-api/v1/update update packages in repository, actually it is just alias for add
|
||||
|
||||
GET /service-api/v1/search search for substring in AUR
|
||||
|
||||
GET /status-api/v1/ahriman get current service status
|
||||
POST /status-api/v1/ahriman update service status
|
||||
@ -52,13 +60,21 @@ def setup_routes(application: Application) -> None:
|
||||
|
||||
GET /status-api/v1/status get web service status itself
|
||||
|
||||
POST /user-api/v1/login login to service
|
||||
POST /user-api/v1/logout logout from service
|
||||
|
||||
:param application: web application instance
|
||||
"""
|
||||
application.router.add_get("/", IndexView, allow_head=True)
|
||||
application.router.add_get("/index.html", IndexView, allow_head=True)
|
||||
|
||||
application.router.add_post("/login", LoginView)
|
||||
application.router.add_post("/logout", LogoutView)
|
||||
application.router.add_post("/service-api/v1/add", AddView)
|
||||
|
||||
application.router.add_post("/service-api/v1/remove", RemoveView)
|
||||
|
||||
application.router.add_get("/service-api/v1/search", SearchView, allow_head=False)
|
||||
|
||||
application.router.add_post("/service-api/v1/update", AddView)
|
||||
|
||||
application.router.add_get("/status-api/v1/ahriman", AhrimanView, allow_head=True)
|
||||
application.router.add_post("/status-api/v1/ahriman", AhrimanView)
|
||||
@ -71,3 +87,6 @@ def setup_routes(application: Application) -> None:
|
||||
application.router.add_post("/status-api/v1/packages/{package}", PackageView)
|
||||
|
||||
application.router.add_get("/status-api/v1/status", StatusView, allow_head=True)
|
||||
|
||||
application.router.add_post("/user-api/v1/login", LoginView)
|
||||
application.router.add_post("/user-api/v1/logout", LogoutView)
|
||||
|
@ -18,9 +18,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp.web import View
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from ahriman.core.auth.auth import Auth
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
|
||||
|
||||
@ -37,6 +38,14 @@ class BaseView(View):
|
||||
watcher: Watcher = self.request.app["watcher"]
|
||||
return watcher
|
||||
|
||||
@property
|
||||
def spawner(self) -> Spawn:
|
||||
"""
|
||||
:return: external process spawner instance
|
||||
"""
|
||||
spawner: Spawn = self.request.app["spawn"]
|
||||
return spawner
|
||||
|
||||
@property
|
||||
def validator(self) -> Auth:
|
||||
"""
|
||||
@ -45,13 +54,33 @@ class BaseView(View):
|
||||
validator: Auth = self.request.app["validator"]
|
||||
return validator
|
||||
|
||||
async def extract_data(self) -> Dict[str, Any]:
|
||||
async def extract_data(self, list_keys: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
extract json data from either json or form data
|
||||
:param list_keys: optional list of keys which must be forced to list from form data
|
||||
:return: raw json object or form data converted to json
|
||||
"""
|
||||
try:
|
||||
json: Dict[str, Any] = await self.request.json()
|
||||
return json
|
||||
except ValueError:
|
||||
return dict(await self.request.post())
|
||||
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
|
||||
:param list_keys: list of keys which must be forced to list from form data
|
||||
:return: form data converted to json. In case if a key is found multiple times it will be returned as list
|
||||
"""
|
||||
raw = await self.request.post()
|
||||
json: Dict[str, Any] = {}
|
||||
for key, value in raw.items():
|
||||
if key in json and isinstance(json[key], list):
|
||||
json[key].append(value)
|
||||
elif key in json:
|
||||
json[key] = [json[key], value]
|
||||
elif key in list_keys:
|
||||
json[key] = [value]
|
||||
else:
|
||||
json[key] = value
|
||||
return json
|
||||
|
19
src/ahriman/web/views/service/__init__.py
Normal file
19
src/ahriman/web/views/service/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (c) 2021 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/>.
|
||||
#
|
52
src/ahriman/web/views/service/add.py
Normal file
52
src/ahriman/web/views/service/add.py
Normal file
@ -0,0 +1,52 @@
|
||||
#
|
||||
# Copyright (c) 2021 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 HTTPFound, Response, json_response
|
||||
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class AddView(BaseView):
|
||||
"""
|
||||
add package web view
|
||||
"""
|
||||
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
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
|
||||
"build_now": true # optional flag which runs build
|
||||
}
|
||||
|
||||
:return: redirect to main page on success
|
||||
"""
|
||||
data = await self.extract_data(["packages"])
|
||||
|
||||
try:
|
||||
now = data.get("build_now", True)
|
||||
packages = data["packages"]
|
||||
except Exception as e:
|
||||
return json_response(text=str(e), status=400)
|
||||
|
||||
self.spawner.packages_add(packages, now)
|
||||
|
||||
return HTTPFound("/")
|
50
src/ahriman/web/views/service/remove.py
Normal file
50
src/ahriman/web/views/service/remove.py
Normal file
@ -0,0 +1,50 @@
|
||||
#
|
||||
# Copyright (c) 2021 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 HTTPFound, Response, json_response
|
||||
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class RemoveView(BaseView):
|
||||
"""
|
||||
remove package web view
|
||||
"""
|
||||
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
remove existing packages
|
||||
|
||||
JSON body must be supplied, the following model is used:
|
||||
{
|
||||
"packages": "ahriman", # either list of packages or package name
|
||||
}
|
||||
|
||||
:return: redirect to main page on success
|
||||
"""
|
||||
data = await self.extract_data(["packages"])
|
||||
|
||||
try:
|
||||
packages = data["packages"]
|
||||
except Exception as e:
|
||||
return json_response(text=str(e), status=400)
|
||||
|
||||
self.spawner.packages_remove(packages)
|
||||
|
||||
return HTTPFound("/")
|
48
src/ahriman/web/views/service/search.py
Normal file
48
src/ahriman/web/views/service/search.py
Normal file
@ -0,0 +1,48 @@
|
||||
#
|
||||
# Copyright (c) 2021 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 aur # type: ignore
|
||||
|
||||
from aiohttp.web import Response, json_response
|
||||
from typing import Iterator
|
||||
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class SearchView(BaseView):
|
||||
"""
|
||||
AUR search web view
|
||||
"""
|
||||
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
search packages in AUR
|
||||
|
||||
search string (non empty) must be supplied as `for` parameter
|
||||
|
||||
:return: 200 with found package bases sorted by name
|
||||
"""
|
||||
search: Iterator[str] = filter(lambda s: len(s) > 3, self.request.query.getall("for", default=[]))
|
||||
search_string = " ".join(search)
|
||||
|
||||
if not search_string:
|
||||
return json_response(text="Search string must not be empty", status=400)
|
||||
packages = aur.search(search_string)
|
||||
|
||||
return json_response(sorted(package.package_base for package in packages))
|
19
src/ahriman/web/views/status/__init__.py
Normal file
19
src/ahriman/web/views/status/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (c) 2021 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/>.
|
||||
#
|
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
|
||||
from aiohttp.web import HTTPNoContent, Response, json_response
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.web.views.base import BaseView
|
||||
@ -51,7 +51,7 @@ class AhrimanView(BaseView):
|
||||
try:
|
||||
status = BuildStatusEnum(data["status"])
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(text=str(e))
|
||||
return json_response(text=str(e), status=400)
|
||||
|
||||
self.service.update_self(status)
|
||||
|
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
|
||||
from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
|
||||
|
||||
from ahriman.core.exceptions import UnknownPackage
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
@ -80,11 +80,11 @@ class PackageView(BaseView):
|
||||
package = Package.from_json(data["package"]) if "package" in data else None
|
||||
status = BuildStatusEnum(data["status"])
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(text=str(e))
|
||||
return json_response(text=str(e), status=400)
|
||||
|
||||
try:
|
||||
self.service.update(base, status, package)
|
||||
except UnknownPackage:
|
||||
raise HTTPBadRequest(text=f"Package {base} is unknown, but no package body set")
|
||||
return json_response(text=f"Package {base} is unknown, but no package body set", status=400)
|
||||
|
||||
return HTTPNoContent()
|
19
src/ahriman/web/views/user/__init__.py
Normal file
19
src/ahriman/web/views/user/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (c) 2021 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/>.
|
||||
#
|
@ -26,6 +26,7 @@ from aiohttp import web
|
||||
from ahriman.core.auth.auth import Auth
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InitializeException
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.web.middlewares.exception_handler import exception_handler
|
||||
from ahriman.web.routes import setup_routes
|
||||
@ -67,11 +68,12 @@ def run_server(application: web.Application) -> None:
|
||||
access_log=logging.getLogger("http"))
|
||||
|
||||
|
||||
def setup_service(architecture: str, configuration: Configuration) -> web.Application:
|
||||
def setup_service(architecture: str, configuration: Configuration, spawner: Spawn) -> web.Application:
|
||||
"""
|
||||
create web application
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param spawner: spawner thread
|
||||
:return: web application instance
|
||||
"""
|
||||
application = web.Application(logger=logging.getLogger("http"))
|
||||
@ -93,6 +95,9 @@ def setup_service(architecture: str, configuration: Configuration) -> web.Applic
|
||||
application.logger.info("setup watcher")
|
||||
application["watcher"] = Watcher(architecture, configuration)
|
||||
|
||||
application.logger.info("setup process spawner")
|
||||
application["spawn"] = spawner
|
||||
|
||||
application.logger.info("setup authorization")
|
||||
validator = application["validator"] = Auth.load(configuration)
|
||||
if validator.enabled:
|
||||
|
Reference in New Issue
Block a user