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)