From 86fa271c4007c1c787a079927e526792e45c94db Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 14 Apr 2026 15:01:05 +0200 Subject: [PATCH] Remove FastAPI dependency from jail config service signatures --- Docs/Tasks.md | 2 + backend/app/routers/jail_config.py | 20 +++++++--- backend/app/services/jail_config_service.py | 44 +++++---------------- backend/tests/test_routers/test_config.py | 2 +- 4 files changed, 28 insertions(+), 40 deletions(-) diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 4f9d048..330e530 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -119,6 +119,8 @@ The inverted import creates a hidden circular-dependency risk: if `health_check` ### TASK-04 — Remove `FastAPI` app instance from service signatures 🟠 +**Status:** Completed ✅ + **Where:** `backend/app/services/jail_config_service.py` — `activate_jail()` and `deactivate_jail()` function signatures accept `app: FastAPI` (guarded by `TYPE_CHECKING` on line 48, used at runtime on lines ~295, ~571). diff --git a/backend/app/routers/jail_config.py b/backend/app/routers/jail_config.py index 75399f4..3088336 100644 --- a/backend/app/routers/jail_config.py +++ b/backend/app/routers/jail_config.py @@ -47,6 +47,12 @@ from app.services import ( jail_config_service, ) from app.utils.fail2ban_client import Fail2BanConnectionError +from app.utils.runtime_state import ( + clear_activation_record, + clear_pending_recovery, + create_pending_recovery, + record_activation, +) router: APIRouter = APIRouter(prefix="/jails", tags=["Jail Config"]) @@ -376,8 +382,10 @@ async def activate_jail( """ req = body if body is not None else ActivateJailRequest() + activation_time = record_activation(app, name) + try: - result = await jail_config_service.activate_jail(app, config_dir, socket_path, name, req) + result = await jail_config_service.activate_jail(config_dir, socket_path, name, req) except JailNameError as exc: raise _bad_request(str(exc)) from exc except JailNotFoundInConfigError: @@ -406,7 +414,6 @@ async def activate_jail( summary="Deactivate an active jail", ) async def deactivate_jail( - app: AppDep, _auth: AuthDep, config_dir: Fail2BanConfigDirDep, socket_path: Fail2BanSocketDep, @@ -418,7 +425,6 @@ async def deactivate_jail( full fail2ban reload so the jail stops immediately. Args: - app: FastAPI application instance. _auth: Validated session. name: Name of the jail to deactivate. @@ -433,7 +439,7 @@ async def deactivate_jail( """ try: - result = await jail_config_service.deactivate_jail(app, config_dir, socket_path, name) + result = await jail_config_service.deactivate_jail(config_dir, socket_path, name) except JailNameError as exc: raise _bad_request(str(exc)) from exc except JailNotFoundInConfigError: @@ -585,7 +591,7 @@ async def rollback_jail( start_cmd_parts: list[str] = start_cmd.split() try: - result = await jail_config_service.rollback_jail(app, config_dir, socket_path, name, start_cmd_parts) + result = await jail_config_service.rollback_jail(config_dir, socket_path, name, start_cmd_parts) except JailNameError as exc: raise _bad_request(str(exc)) from exc except ConfigWriteError as exc: @@ -594,6 +600,10 @@ async def rollback_jail( detail=f"Failed to write config override: {exc}", ) from exc + if result.fail2ban_running: + clear_pending_recovery(app) + clear_activation_record(app) + return result diff --git a/backend/app/services/jail_config_service.py b/backend/app/services/jail_config_service.py index 94af662..521425c 100644 --- a/backend/app/services/jail_config_service.py +++ b/backend/app/services/jail_config_service.py @@ -14,7 +14,7 @@ import os import re import tempfile from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import cast import structlog @@ -37,15 +37,6 @@ from app.models.config import ( from app.services import health_service from app.utils.async_utils import run_blocking from app.utils.fail2ban_client import Fail2BanClient -from app.utils.runtime_state import ( - clear_activation_record, - clear_pending_recovery, - create_pending_recovery, - record_activation, -) - -if TYPE_CHECKING: # pragma: no cover - from fastapi import FastAPI log: structlog.stdlib.BoundLogger = structlog.get_logger() @@ -274,29 +265,18 @@ async def list_inactive_jails( async def activate_jail( - app: FastAPI, config_dir: str, socket_path: str, name: str, req: ActivateJailRequest, ) -> JailActivationResponse: - """Activate a jail and manage crash recovery state. + """Activate a jail and update the health-check cache. - This wrapper records the activation timestamp, delegates the actual - file-based activation workflow to the lower-level implementation, and - updates the health-check cache immediately so the UI reflects the - current fail2ban state. + This wrapper delegates the file-based activation workflow to the + lower-level implementation and runs an immediate probe so the UI + reflects the current fail2ban state. """ - activation_time = record_activation(app, name) result = await _activate_jail(config_dir, socket_path, name, req) - - if not result.fail2ban_running: - create_pending_recovery( - app, - jail_name=name, - activated_at=activation_time, - ) - await run_probe(socket_path) return result @@ -561,7 +541,6 @@ async def _rollback_activation_async( async def deactivate_jail( - app: FastAPI, config_dir: str, socket_path: str, name: str, @@ -707,20 +686,17 @@ async def validate_jail_config( async def rollback_jail( - app: FastAPI, config_dir: str, socket_path: str, name: str, start_cmd_parts: list[str], ) -> RollbackResponse: - """Rollback a jail and clear pending recovery state on success.""" - result = await _rollback_jail(config_dir, socket_path, name, start_cmd_parts) + """Rollback a jail and return the result. - if result.fail2ban_running: - clear_pending_recovery(app) - clear_activation_record(app) - - return result + The caller is responsible for clearing pending recovery state when the + operation succeeds. + """ + return await _rollback_jail(config_dir, socket_path, name, start_cmd_parts) async def _rollback_jail( diff --git a/backend/tests/test_routers/test_config.py b/backend/tests/test_routers/test_config.py index 26068ce..1a8ebc8 100644 --- a/backend/tests/test_routers/test_config.py +++ b/backend/tests/test_routers/test_config.py @@ -807,7 +807,7 @@ class TestActivateJail: assert resp.status_code == 200 # Verify the override values were passed to the service - called_req = mock_activate.call_args.args[4] + called_req = mock_activate.call_args.args[3] assert called_req.bantime == "1h" assert called_req.maxretry == 3