diff --git a/recipes/check/compose.yml b/recipes/check/compose.yml index a34c2bfd..0a680310 100644 --- a/recipes/check/compose.yml +++ b/recipes/check/compose.yml @@ -25,6 +25,11 @@ services: volume: nocopy: true + healthcheck: + test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info + interval: 10s + start_period: 30s + command: web frontend: @@ -46,8 +51,6 @@ services: worker: image: arcan1s/ahriman:edge - depends_on: - - backend privileged: true environment: @@ -63,6 +66,10 @@ services: volume: nocopy: true + depends_on: + backend: + condition: service_healthy + command: repo-daemon --dry-run configs: diff --git a/recipes/distributed-manual/compose.yml b/recipes/distributed-manual/compose.yml index 29bf7148..f6d82e58 100644 --- a/recipes/distributed-manual/compose.yml +++ b/recipes/distributed-manual/compose.yml @@ -25,6 +25,11 @@ services: volume: nocopy: true + healthcheck: + test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info + interval: 10s + start_period: 30s + command: web frontend: diff --git a/recipes/distributed/compose.yml b/recipes/distributed/compose.yml index 4cf75d39..db863ace 100644 --- a/recipes/distributed/compose.yml +++ b/recipes/distributed/compose.yml @@ -25,6 +25,11 @@ services: volume: nocopy: true + healthcheck: + test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info + interval: 10s + start_period: 30s + command: web frontend: @@ -67,6 +72,11 @@ services: secrets: - password + healthcheck: + test: curl --fail --silent --output /dev/null http://worker:8080/api/v1/info + interval: 10s + start_period: 30s + command: web configs: diff --git a/recipes/i686/compose.yml b/recipes/i686/compose.yml index a6899d1a..ed3abadf 100644 --- a/recipes/i686/compose.yml +++ b/recipes/i686/compose.yml @@ -33,6 +33,11 @@ services: volume: nocopy: true + healthcheck: + test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info + interval: 10s + start_period: 30s + command: web frontend: diff --git a/recipes/multirepo/compose.yml b/recipes/multirepo/compose.yml index 1a674b03..77003170 100644 --- a/recipes/multirepo/compose.yml +++ b/recipes/multirepo/compose.yml @@ -26,6 +26,11 @@ services: volume: nocopy: true + healthcheck: + test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info + interval: 10s + start_period: 30s + command: web frontend: diff --git a/recipes/web/compose.yml b/recipes/web/compose.yml index 4ebf3f22..2030617c 100644 --- a/recipes/web/compose.yml +++ b/recipes/web/compose.yml @@ -25,6 +25,11 @@ services: volume: nocopy: true + healthcheck: + test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info + interval: 10s + start_period: 30s + command: web frontend: diff --git a/src/ahriman/web/schemas/__init__.py b/src/ahriman/web/schemas/__init__.py index 2fe4d73f..998d6ee6 100644 --- a/src/ahriman/web/schemas/__init__.py +++ b/src/ahriman/web/schemas/__init__.py @@ -24,6 +24,7 @@ from ahriman.web.schemas.changes_schema import ChangesSchema from ahriman.web.schemas.counters_schema import CountersSchema from ahriman.web.schemas.error_schema import ErrorSchema from ahriman.web.schemas.file_schema import FileSchema +from ahriman.web.schemas.info_schema import InfoSchema from ahriman.web.schemas.internal_status_schema import InternalStatusSchema from ahriman.web.schemas.log_schema import LogSchema from ahriman.web.schemas.login_schema import LoginSchema diff --git a/src/ahriman/web/schemas/info_schema.py b/src/ahriman/web/schemas/info_schema.py new file mode 100644 index 00000000..83ae4ae8 --- /dev/null +++ b/src/ahriman/web/schemas/info_schema.py @@ -0,0 +1,40 @@ +# +# 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 . +# +from marshmallow import Schema, fields + +from ahriman import __version__ +from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema + + +class InfoSchema(Schema): + """ + response service information schema + """ + + auth = fields.Boolean(dump_default=False, required=True, metadata={ + "description": "Whether authentication is enabled or not", + }) + repositories = fields.Nested(RepositoryIdSchema(many=True), required=True, metadata={ + "description": "List of loaded repositories", + }) + version = fields.String(required=True, metadata={ + "description": "Service version", + "example": __version__, + }) diff --git a/src/ahriman/web/schemas/internal_status_schema.py b/src/ahriman/web/schemas/internal_status_schema.py index 1bf2be33..46122d40 100644 --- a/src/ahriman/web/schemas/internal_status_schema.py +++ b/src/ahriman/web/schemas/internal_status_schema.py @@ -37,6 +37,6 @@ class InternalStatusSchema(RepositoryIdSchema): "description": "Repository status as stored by web service", }) version = fields.String(required=True, metadata={ - "description": "Repository version", + "description": "Service version", "example": __version__, }) diff --git a/src/ahriman/web/views/v1/status/info.py b/src/ahriman/web/views/v1/status/info.py new file mode 100644 index 00000000..b8ad5dff --- /dev/null +++ b/src/ahriman/web/views/v1/status/info.py @@ -0,0 +1,70 @@ +# +# 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 . +# +import aiohttp_apispec # type: ignore[import-untyped] + +from aiohttp.web import Response, json_response + +from ahriman import __version__ +from ahriman.models.user_access import UserAccess +from ahriman.web.schemas import AuthSchema, ErrorSchema, InfoSchema +from ahriman.web.views.base import BaseView + + +class InfoView(BaseView): + """ + web service information view + + Attributes: + GET_PERMISSION(UserAccess): (class attribute) get permissions of self + """ + + GET_PERMISSION = UserAccess.Unauthorized + ROUTES = ["/api/v1/info"] + + @aiohttp_apispec.docs( + tags=["Status"], + summary="Service information", + description="Perform basic service health check and returns its information", + responses={ + 200: {"description": "Success response", "schema": InfoSchema}, + 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 service information + + Returns: + Response: 200 with service information object + """ + response = { + "auth": self.validator.enabled, + "repositories": [ + repository_id.view() + for repository_id in sorted(self.services) + ], + "version": __version__, + } + + return json_response(response) diff --git a/tests/ahriman/web/schemas/test_info_schema.py b/tests/ahriman/web/schemas/test_info_schema.py new file mode 100644 index 00000000..1982fb6b --- /dev/null +++ b/tests/ahriman/web/schemas/test_info_schema.py @@ -0,0 +1 @@ +# schema testing goes in view class tests diff --git a/tests/ahriman/web/views/v1/status/test_view_v1_status_info.py b/tests/ahriman/web/views/v1/status/test_view_v1_status_info.py new file mode 100644 index 00000000..3e72deaf --- /dev/null +++ b/tests/ahriman/web/views/v1/status/test_view_v1_status_info.py @@ -0,0 +1,40 @@ +import pytest + +from aiohttp.test_utils import TestClient + +from ahriman import __version__ +from ahriman.models.repository_id import RepositoryId +from ahriman.models.user_access import UserAccess +from ahriman.web.views.v1.status.info import InfoView + + +async def test_get_permission() -> None: + """ + must return correct permission for the request + """ + for method in ("GET",): + request = pytest.helpers.request("", "", method) + assert await InfoView.get_permission(request) == UserAccess.Unauthorized + + +def test_routes() -> None: + """ + must return correct routes + """ + assert InfoView.ROUTES == ["/api/v1/info"] + + +async def test_get(client: TestClient, repository_id: RepositoryId) -> None: + """ + must return service information + """ + response_schema = pytest.helpers.schema_response(InfoView.get) + + response = await client.get(f"/api/v1/info") + assert response.ok + json = await response.json() + assert not response_schema.validate(json) + + assert json["repositories"] == [repository_id.view()] + assert not json["auth"] + assert json["version"] == __version__ diff --git a/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py b/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py index 4c7ebfea..cd6f54f8 100644 --- a/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py +++ b/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py @@ -25,7 +25,7 @@ def test_routes() -> None: async def test_get(client: TestClient, repository_id: RepositoryId) -> None: """ - must return status for specific package + must return list of available repositories """ response_schema = pytest.helpers.schema_response(RepositoriesView.get)