Refactor config file service facade wrappers and mark TASK-06 complete in Docs/Tasks.md
This commit is contained in:
@@ -25,7 +25,12 @@ from app.exceptions import (
|
||||
JailNotFoundError,
|
||||
JailNotFoundInConfigError,
|
||||
)
|
||||
import app.services.config_file_service as config_file_service
|
||||
import app.services.jail_service as jail_service
|
||||
from app.utils.config_file_utils import (
|
||||
_build_inactive_jail,
|
||||
_probe_fail2ban_running,
|
||||
_safe_jail_name,
|
||||
)
|
||||
from app.models.config import (
|
||||
ActivateJailRequest,
|
||||
InactiveJail,
|
||||
@@ -40,6 +45,21 @@ from app.utils.fail2ban_client import Fail2BanClient
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
|
||||
def _parse_jails_sync(config_dir: Path) -> tuple[dict[str, dict[str, str]], dict[str, str]]:
|
||||
"""Delegate the jail config parse helper through the facade."""
|
||||
from app.services import config_file_service
|
||||
|
||||
return config_file_service._parse_jails_sync(config_dir)
|
||||
|
||||
|
||||
async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||
"""Delegate the active jail lookup helper through the facade."""
|
||||
from app.services import config_file_service
|
||||
|
||||
return await config_file_service._get_active_jail_names(socket_path)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -240,11 +260,11 @@ async def list_inactive_jails(
|
||||
inactive jails.
|
||||
"""
|
||||
parsed_result: tuple[dict[str, dict[str, str]], dict[str, str]] = await run_blocking(
|
||||
config_file_service._parse_jails_sync,
|
||||
_parse_jails_sync,
|
||||
Path(config_dir),
|
||||
)
|
||||
all_jails, source_files = parsed_result
|
||||
active_names: set[str] = await config_file_service._get_active_jail_names(socket_path)
|
||||
active_names: set[str] = await _get_active_jail_names(socket_path)
|
||||
|
||||
inactive: list[InactiveJail] = []
|
||||
for jail_name, settings in sorted(all_jails.items()):
|
||||
@@ -253,7 +273,7 @@ async def list_inactive_jails(
|
||||
continue
|
||||
|
||||
source = source_files.get(jail_name, config_dir)
|
||||
inactive.append(config_file_service.build_inactive_jail(jail_name, settings, source, Path(config_dir)))
|
||||
inactive.append(_build_inactive_jail(jail_name, settings, source, Path(config_dir)))
|
||||
|
||||
log.info(
|
||||
"inactive_jails_listed",
|
||||
@@ -312,21 +332,26 @@ async def _activate_jail(
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the fail2ban
|
||||
socket is unreachable during reload.
|
||||
"""
|
||||
config_file_service.safe_jail_name(name)
|
||||
_safe_jail_name(name)
|
||||
|
||||
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
||||
all_jails, _source_files = await run_blocking(_parse_jails_sync, Path(config_dir))
|
||||
|
||||
if name not in all_jails:
|
||||
raise JailNotFoundInConfigError(name)
|
||||
|
||||
active_names = await config_file_service._get_active_jail_names(socket_path)
|
||||
active_names = await _get_active_jail_names(socket_path)
|
||||
if name in active_names:
|
||||
raise JailAlreadyActiveError(name)
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Pre-activation validation — collect warnings but do not block #
|
||||
# ---------------------------------------------------------------------- #
|
||||
validation_result: JailValidationResult = await run_blocking(config_file_service._validate_jail_config_sync, Path(config_dir), name
|
||||
from app.services import config_file_service
|
||||
|
||||
validation_result: JailValidationResult = await run_blocking(
|
||||
config_file_service._validate_jail_config_sync,
|
||||
Path(config_dir),
|
||||
name,
|
||||
)
|
||||
warnings: list[str] = [f"{i.field}: {i.message}" for i in validation_result.issues]
|
||||
if warnings:
|
||||
@@ -380,6 +405,8 @@ async def _activate_jail(
|
||||
# Activation reload — if it fails, roll back immediately #
|
||||
# ---------------------------------------------------------------------- #
|
||||
try:
|
||||
from app.services import config_file_service
|
||||
|
||||
await config_file_service.jail_service.reload_all(socket_path, include_jails=[name])
|
||||
except JailNotFoundError as exc:
|
||||
# Jail configuration is invalid (e.g. missing logpath that prevents
|
||||
@@ -522,6 +549,8 @@ async def _rollback_activation_async(
|
||||
|
||||
# Step 2 — reload fail2ban with the restored config.
|
||||
try:
|
||||
from app.services import config_file_service
|
||||
|
||||
await config_file_service.jail_service.reload_all(socket_path)
|
||||
log.info("jail_activation_rollback_reload_ok", jail=name)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
@@ -529,6 +558,8 @@ async def _rollback_activation_async(
|
||||
return False
|
||||
|
||||
# Step 3 — wait for fail2ban to come back.
|
||||
from app.services import config_file_service
|
||||
|
||||
for attempt in range(_POST_RELOAD_MAX_ATTEMPTS):
|
||||
if attempt > 0:
|
||||
await asyncio.sleep(_POST_RELOAD_PROBE_INTERVAL)
|
||||
@@ -583,6 +614,8 @@ async def _deactivate_jail(
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the fail2ban
|
||||
socket is unreachable during reload.
|
||||
"""
|
||||
from app.services import config_file_service
|
||||
|
||||
config_file_service.safe_jail_name(name)
|
||||
|
||||
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
||||
@@ -602,6 +635,8 @@ async def _deactivate_jail(
|
||||
)
|
||||
|
||||
try:
|
||||
from app.services import config_file_service
|
||||
|
||||
await config_file_service.jail_service.reload_all(socket_path, exclude_jails=[name])
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log.warning("reload_after_deactivate_failed", jail=name, error=str(exc))
|
||||
@@ -638,6 +673,8 @@ async def delete_jail_local_override(
|
||||
delete the live config file).
|
||||
ConfigWriteError: If the file cannot be deleted.
|
||||
"""
|
||||
from app.services import config_file_service
|
||||
|
||||
config_file_service.safe_jail_name(name)
|
||||
|
||||
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
||||
@@ -678,8 +715,11 @@ async def validate_jail_config(
|
||||
Raises:
|
||||
JailNameError: If *name* contains invalid characters.
|
||||
"""
|
||||
from app.services import config_file_service
|
||||
|
||||
config_file_service.safe_jail_name(name)
|
||||
return await run_blocking(config_file_service._validate_jail_config_sync,
|
||||
return await run_blocking(
|
||||
config_file_service._validate_jail_config_sync,
|
||||
Path(config_dir),
|
||||
name,
|
||||
)
|
||||
@@ -725,7 +765,7 @@ async def _rollback_jail(
|
||||
JailNameError: If *name* contains invalid characters.
|
||||
ConfigWriteError: If writing the ``.local`` file fails.
|
||||
"""
|
||||
config_file_service.safe_jail_name(name)
|
||||
_safe_jail_name(name)
|
||||
|
||||
|
||||
# Write enabled=false — this must succeed even when fail2ban is down.
|
||||
@@ -737,18 +777,23 @@ async def _rollback_jail(
|
||||
)
|
||||
log.info("jail_rolled_back_disabled", jail=name)
|
||||
|
||||
from app.services import config_file_service
|
||||
|
||||
# Attempt to start the daemon.
|
||||
started = await config_file_service.start_daemon(start_cmd_parts)
|
||||
log.info("jail_rollback_start_attempted", jail=name, start_ok=started)
|
||||
|
||||
# Wait for the socket to come back.
|
||||
fail2ban_running = await config_file_service.wait_for_fail2ban(socket_path, max_wait_seconds=10.0, poll_interval=2.0)
|
||||
fail2ban_running = await config_file_service.wait_for_fail2ban(
|
||||
socket_path,
|
||||
max_wait_seconds=10.0,
|
||||
poll_interval=2.0,
|
||||
)
|
||||
|
||||
active_jails = 0
|
||||
if fail2ban_running:
|
||||
names = await config_file_service._get_active_jail_names(socket_path)
|
||||
active_jails = len(names)
|
||||
|
||||
if fail2ban_running:
|
||||
log.info("jail_rollback_success", jail=name, active_jails=active_jails)
|
||||
return RollbackResponse(
|
||||
|
||||
Reference in New Issue
Block a user