Refactor map color threshold storage into dedicated settings service
This commit is contained in:
@@ -5,8 +5,17 @@ from typing import Annotated
|
||||
import structlog
|
||||
from fastapi import APIRouter, HTTPException, Query, Request, status
|
||||
|
||||
from app.dependencies import AuthDep, DbDep, Fail2BanSocketDep, Fail2BanStartCommandDep
|
||||
from app.exceptions import ConfigOperationError, JailOperationError
|
||||
from app.dependencies import (
|
||||
AuthDep,
|
||||
DbDep,
|
||||
Fail2BanSocketDep,
|
||||
Fail2BanStartCommandDep,
|
||||
)
|
||||
from app.exceptions import (
|
||||
ConfigOperationError,
|
||||
Fail2BanConnectionError,
|
||||
JailOperationError,
|
||||
)
|
||||
from app.models.config import (
|
||||
Fail2BanLogResponse,
|
||||
GlobalConfigResponse,
|
||||
@@ -19,9 +28,12 @@ from app.models.config import (
|
||||
RegexTestResponse,
|
||||
ServiceStatusResponse,
|
||||
)
|
||||
from app.services import config_service, jail_service, log_service, setup_service
|
||||
from app.services import (
|
||||
config_service,
|
||||
jail_service,
|
||||
log_service,
|
||||
)
|
||||
from app.utils.config_file_utils import start_daemon, wait_for_fail2ban
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
@@ -41,17 +53,20 @@ def _bad_request(message: str) -> HTTPException:
|
||||
detail=message,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/global",
|
||||
response_model=GlobalConfigResponse,
|
||||
summary="Return global fail2ban settings",
|
||||
)
|
||||
async def get_global_config(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> GlobalConfigResponse:
|
||||
"""Return global fail2ban settings (log level, log target, database config).
|
||||
"""Return global fail2ban settings.
|
||||
|
||||
Includes log level, log target, and database configuration.
|
||||
|
||||
Args:
|
||||
request: Incoming request.
|
||||
@@ -69,15 +84,13 @@ async def get_global_config(
|
||||
raise _bad_gateway(exc) from exc
|
||||
|
||||
|
||||
|
||||
|
||||
@router.put(
|
||||
"/global",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Update global fail2ban settings",
|
||||
)
|
||||
async def update_global_config(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
body: GlobalConfigUpdate,
|
||||
@@ -105,16 +118,13 @@ async def update_global_config(
|
||||
# Reload endpoint
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post(
|
||||
"/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Reload fail2ban to apply configuration changes",
|
||||
)
|
||||
async def reload_fail2ban(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> None:
|
||||
@@ -144,16 +154,13 @@ async def reload_fail2ban(
|
||||
# Restart endpoint
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post(
|
||||
"/restart",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Restart the fail2ban service",
|
||||
)
|
||||
async def restart_fail2ban(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
start_cmd: Fail2BanStartCommandDep,
|
||||
@@ -175,8 +182,8 @@ async def restart_fail2ban(
|
||||
HTTPException: 503 when fail2ban does not come back online within
|
||||
10 seconds after being started. Check the fail2ban log for
|
||||
initialisation errors. Use
|
||||
``POST /api/config/jails/{name}/rollback`` if a specific jail
|
||||
is suspect.
|
||||
``POST /api/config/jails/{name}/rollback``
|
||||
if a specific jail is suspect.
|
||||
"""
|
||||
start_cmd_parts: list[str] = start_cmd.split()
|
||||
|
||||
@@ -194,15 +201,21 @@ async def restart_fail2ban(
|
||||
# Step 2: start the daemon via subprocess.
|
||||
await start_daemon(start_cmd_parts)
|
||||
|
||||
# Step 3: probe the socket until fail2ban is responsive or the budget expires.
|
||||
fail2ban_running: bool = await wait_for_fail2ban(socket_path, max_wait_seconds=10.0)
|
||||
# Step 3: probe the socket until fail2ban is responsive or the budget
|
||||
# expires.
|
||||
fail2ban_running: bool = await wait_for_fail2ban(
|
||||
socket_path,
|
||||
max_wait_seconds=10.0,
|
||||
)
|
||||
if not fail2ban_running:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=(
|
||||
"fail2ban was stopped but did not come back online within 10 seconds. "
|
||||
"fail2ban was stopped but did not come back "
|
||||
"online within 10 seconds. "
|
||||
"Check the fail2ban log for initialisation errors. "
|
||||
"Use POST /api/config/jails/{name}/rollback if a specific jail is suspect."
|
||||
"Use POST /api/config/jails/{name}/rollback if a "
|
||||
"specific jail is suspect."
|
||||
),
|
||||
)
|
||||
log.info("fail2ban_restarted")
|
||||
@@ -212,9 +225,6 @@ async def restart_fail2ban(
|
||||
# Regex tester (stateless)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post(
|
||||
"/regex-test",
|
||||
response_model=RegexTestResponse,
|
||||
@@ -234,7 +244,8 @@ async def regex_test(
|
||||
body: Sample log line and regex pattern.
|
||||
|
||||
Returns:
|
||||
:class:`~app.models.config.RegexTestResponse` with match result and groups.
|
||||
:class:`~app.models.config.RegexTestResponse` with match result and
|
||||
groups.
|
||||
"""
|
||||
return log_service.test_regex(body)
|
||||
|
||||
@@ -243,9 +254,6 @@ async def regex_test(
|
||||
# Log path management
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post(
|
||||
"/preview-log",
|
||||
response_model=LogPreviewResponse,
|
||||
@@ -275,16 +283,13 @@ async def preview_log(
|
||||
# Map color thresholds
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
@router.get(
|
||||
"/map-color-thresholds",
|
||||
response_model=MapColorThresholdsResponse,
|
||||
summary="Get map color threshold configuration",
|
||||
)
|
||||
async def get_map_color_thresholds(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
db: DbDep,
|
||||
) -> MapColorThresholdsResponse:
|
||||
@@ -298,16 +303,7 @@ async def get_map_color_thresholds(
|
||||
:class:`~app.models.config.MapColorThresholdsResponse` with
|
||||
current thresholds.
|
||||
"""
|
||||
high, medium, low = await setup_service.get_map_color_thresholds(db)
|
||||
return MapColorThresholdsResponse(
|
||||
threshold_high=high,
|
||||
threshold_medium=medium,
|
||||
threshold_low=low,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
return await config_service.get_map_color_thresholds(db)
|
||||
|
||||
@router.put(
|
||||
"/map-color-thresholds",
|
||||
@@ -315,7 +311,7 @@ async def get_map_color_thresholds(
|
||||
summary="Update map color threshold configuration",
|
||||
)
|
||||
async def update_map_color_thresholds(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
db: DbDep,
|
||||
body: MapColorThresholdsUpdate,
|
||||
@@ -336,22 +332,11 @@ async def update_map_color_thresholds(
|
||||
properly ordered).
|
||||
"""
|
||||
try:
|
||||
await setup_service.set_map_color_thresholds(
|
||||
db,
|
||||
threshold_high=body.threshold_high,
|
||||
threshold_medium=body.threshold_medium,
|
||||
threshold_low=body.threshold_low,
|
||||
)
|
||||
await config_service.update_map_color_thresholds(db, body)
|
||||
except ValueError as exc:
|
||||
raise _bad_request(str(exc)) from exc
|
||||
|
||||
return MapColorThresholdsResponse(
|
||||
threshold_high=body.threshold_high,
|
||||
threshold_medium=body.threshold_medium,
|
||||
threshold_low=body.threshold_low,
|
||||
)
|
||||
|
||||
|
||||
return await config_service.get_map_color_thresholds(db)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -360,13 +345,26 @@ async def update_map_color_thresholds(
|
||||
summary="Read the tail of the fail2ban daemon log file",
|
||||
)
|
||||
async def get_fail2ban_log(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
lines: Annotated[int, Query(ge=1, le=2000, description="Number of lines to return from the tail.")] = 200,
|
||||
filter: Annotated[ # noqa: A002
|
||||
lines: Annotated[
|
||||
int,
|
||||
Query(
|
||||
ge=1,
|
||||
le=2000,
|
||||
description="Number of lines to return from the tail.",
|
||||
),
|
||||
] = 200,
|
||||
filter_: Annotated[ # noqa: A002
|
||||
str | None,
|
||||
Query(description="Plain-text substring filter; only matching lines are returned."),
|
||||
Query(
|
||||
alias="filter",
|
||||
description=(
|
||||
"Plain-text substring filter; "
|
||||
"only matching lines are returned."
|
||||
),
|
||||
),
|
||||
] = None,
|
||||
) -> Fail2BanLogResponse:
|
||||
"""Return the tail of the fail2ban daemon log file.
|
||||
@@ -390,22 +388,20 @@ async def get_fail2ban_log(
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
try:
|
||||
return await config_service.read_fail2ban_log(socket_path, lines, filter)
|
||||
return await log_service.read_fail2ban_log(socket_path, lines, filter_)
|
||||
except ConfigOperationError as exc:
|
||||
raise _bad_request(str(exc)) from exc
|
||||
except Fail2BanConnectionError as exc:
|
||||
raise _bad_gateway(exc) from exc
|
||||
|
||||
|
||||
|
||||
|
||||
@router.get(
|
||||
"/service-status",
|
||||
response_model=ServiceStatusResponse,
|
||||
summary="Return fail2ban service health status with log configuration",
|
||||
)
|
||||
async def get_service_status(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> ServiceStatusResponse:
|
||||
@@ -428,11 +424,9 @@ async def get_service_status(
|
||||
from app.services import health_service
|
||||
|
||||
try:
|
||||
return await config_service.get_service_status(
|
||||
return await health_service.get_service_status(
|
||||
socket_path,
|
||||
probe_fn=health_service.probe,
|
||||
)
|
||||
except Fail2BanConnectionError as exc:
|
||||
raise _bad_gateway(exc) from exc
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user