diff --git a/src/ahriman/core/status/watcher.py b/src/ahriman/core/status/watcher.py index e341b2a0..7d2f6502 100644 --- a/src/ahriman/core/status/watcher.py +++ b/src/ahriman/core/status/watcher.py @@ -23,6 +23,7 @@ from typing import Any, Self from ahriman.core.exceptions import UnknownPackageError from ahriman.core.log import LazyLogging +from ahriman.core.repository.package_info import PackageInfo from ahriman.core.status import Client from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.changes import Changes @@ -39,15 +40,18 @@ class Watcher(LazyLogging): Attributes: client(Client): reporter instance + package_info(PackageInfo): package info instance status(BuildStatus): daemon status """ - def __init__(self, client: Client) -> None: + def __init__(self, client: Client, package_info: PackageInfo) -> None: """ Args: client(Client): reporter instance + package_info(PackageInfo): package info instance """ self.client = client + self.package_info = package_info self._lock = Lock() self._known: dict[str, tuple[Package, BuildStatus]] = {} @@ -80,6 +84,15 @@ class Watcher(LazyLogging): logs_rotate: Callable[[int], None] + def package_archives(self, package_base: str) -> list[Package]: + """ + get known package archives + + Returns: + list[Package]: list of built package for this package base + """ + return self.package_info.package_archives(package_base) + package_changes_get: Callable[[str], Changes] package_changes_update: Callable[[str, Changes], None] diff --git a/src/ahriman/web/views/v1/packages/archives.py b/src/ahriman/web/views/v1/packages/archives.py new file mode 100644 index 00000000..684d6332 --- /dev/null +++ b/src/ahriman/web/views/v1/packages/archives.py @@ -0,0 +1,65 @@ +# +# Copyright (c) 2021-2026 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 . +# +from aiohttp.web import Response +from typing import ClassVar + +from ahriman.models.user_access import UserAccess +from ahriman.web.apispec.decorators import apidocs +from ahriman.web.schemas import PackageNameSchema, PackageSchema, RepositoryIdSchema +from ahriman.web.views.base import BaseView +from ahriman.web.views.status_view_guard import StatusViewGuard + + +class Archives(StatusViewGuard, BaseView): + """ + package archives web view + + Attributes: + GET_PERMISSION(UserAccess): (class attribute) get permissions of self + """ + + GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter + ROUTES = ["/api/v1/packages/{package}/archives"] + + @apidocs( + tags=["Packages"], + summary="Get package archives", + description="Retrieve built package archives for the base", + permission=GET_PERMISSION, + error_404_description="Package base and/or repository are unknown", + schema=PackageSchema(many=True), + match_schema=PackageNameSchema, + query_schema=RepositoryIdSchema, + ) + async def get(self) -> Response: + """ + get package changes + + Returns: + Response: 200 with package change on success + + Raises: + HTTPNotFound: if package base is unknown + """ + package_base = self.request.match_info["package"] + + archives = self.service(package_base=package_base).package_archives(package_base) + + return self.json_response([archive.view() for archive in archives]) diff --git a/src/ahriman/web/web.py b/src/ahriman/web/web.py index 348ab66f..f2ff732f 100644 --- a/src/ahriman/web/web.py +++ b/src/ahriman/web/web.py @@ -23,12 +23,14 @@ import logging import socket from aiohttp.web import Application, normalize_path_middleware, run_app +from pathlib import Path from ahriman.core.auth import Auth from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.distributed import WorkersCache from ahriman.core.exceptions import InitializeError +from ahriman.core.repository.package_info import PackageInfo from ahriman.core.spawn import Spawn from ahriman.core.status import Client from ahriman.core.status.watcher import Watcher @@ -78,6 +80,33 @@ def _create_socket(configuration: Configuration, application: Application) -> so return sock +def _create_watcher(path: Path, repository_id: RepositoryId) -> Watcher: + """ + build watcher for selected repository + + Args: + path(Path): path to configuration file + repository_id(RepositoryId): repository unique identifier + + Returns: + Watcher: watcher instance + """ + logging.getLogger(__name__).info("load repository %s", repository_id) + # load settings explicitly for architecture if any + configuration = Configuration.from_path(path, repository_id) + + # load database instance, because it holds identifier + database = SQLite.load(configuration) + # explicitly load local client + client = Client.load(repository_id, configuration, database, report=False) + + # load package info wrapper + package_info = PackageInfo() + package_info.configuration = configuration + + return Watcher(client, package_info) + + async def _on_shutdown(application: Application) -> None: """ web application shutdown handler @@ -168,18 +197,11 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis # package cache if not repositories: raise InitializeError("No repositories configured, exiting") - watchers: dict[RepositoryId, Watcher] = {} configuration_path, _ = configuration.check_loaded() - for repository_id in repositories: - application.logger.info("load repository %s", repository_id) - # load settings explicitly for architecture if any - repository_configuration = Configuration.from_path(configuration_path, repository_id) - # load database instance, because it holds identifier - database = SQLite.load(repository_configuration) - # explicitly load local client - client = Client.load(repository_id, repository_configuration, database, report=False) - watchers[repository_id] = Watcher(client) - application[WatcherKey] = watchers + application[WatcherKey] = { + repository_id: _create_watcher(configuration_path, repository_id) + for repository_id in repositories + } # workers cache application[WorkersKey] = WorkersCache(configuration) # process spawner