Task 3: remove config_file_service facade, update direct imports and tests

This commit is contained in:
2026-04-15 21:16:00 +02:00
parent 0e22d1c425
commit cdb0c3681e
10 changed files with 385 additions and 1580 deletions

View File

@@ -283,8 +283,8 @@ class FilterConfig(BaseModel):
The ``active``, ``used_by_jails``, ``source_file``, and
``has_local_override`` fields are populated by
:func:`~app.services.config_file_service.list_filters` and
:func:`~app.services.config_file_service.get_filter`. When the model is
:func:`~app.services.filter_config_service.list_filters` and
:func:`~app.services.filter_config_service.get_filter`. When the model is
returned from the raw file-based endpoints (``/filters/{name}/parsed``),
these fields carry their default values.
"""
@@ -326,7 +326,7 @@ class FilterConfig(BaseModel):
default=None,
description="Systemd journal match expression.",
)
# Active-status fields — populated by config_file_service.list_filters /
# Active-status fields — populated by filter_config_service.list_filters /
# get_filter; default to safe "inactive" values when not computed.
active: bool = Field(
default=False,
@@ -512,7 +512,7 @@ class ActionConfig(BaseModel):
default_factory=dict,
description="Runtime parameters that can be overridden per jail.",
)
# Active-status fields — populated by config_file_service.list_actions /
# Active-status fields — populated by action_config_service.list_actions /
# get_action; default to safe "inactive" values when not computed.
active: bool = Field(
default=False,

View File

@@ -19,7 +19,8 @@ from app.models.config import (
RegexTestResponse,
ServiceStatusResponse,
)
from app.services import config_file_service, config_service, jail_service, log_service, setup_service
from app.services import config_service, jail_service, log_service, setup_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()
@@ -191,10 +192,10 @@ async def restart_fail2ban(
raise _bad_gateway(exc) from exc
# Step 2: start the daemon via subprocess.
await config_file_service.start_daemon(start_cmd_parts)
await start_daemon(start_cmd_parts)
# Step 3: probe the socket until fail2ban is responsive or the budget expires.
fail2ban_running: bool = await config_file_service.wait_for_fail2ban(socket_path, max_wait_seconds=10.0)
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,

View File

@@ -27,8 +27,8 @@ from app.exceptions import (
)
import app.services.jail_service as jail_service
from app.utils.config_file_utils import (
_get_active_jail_names,
_parse_jails_sync,
_get_active_jail_names as _config_file_get_active_jail_names,
_parse_jails_sync as _config_file_parse_jails_sync,
_safe_jail_name,
build_parser,
)
@@ -50,15 +50,11 @@ log: structlog.stdlib.BoundLogger = structlog.get_logger()
# ---------------------------------------------------------------------------
def _parse_jails_sync(config_dir: Path) -> tuple[dict[str, dict[str, str]], Path]:
from app.services import config_file_service
return config_file_service._parse_jails_sync(config_dir)
return _config_file_parse_jails_sync(config_dir)
async def _get_active_jail_names(socket_path: str) -> set[str]:
from app.services import config_file_service
return await config_file_service._get_active_jail_names(socket_path)
return await _config_file_get_active_jail_names(socket_path)
# ---------------------------------------------------------------------------
@@ -498,8 +494,6 @@ async def list_actions(
"""
action_d = Path(config_dir) / "action.d"
from app.services import config_file_service
raw_actions: list[tuple[str, str, str, bool, str]] = await run_blocking(_parse_actions_sync, action_d)
all_jails_result, active_names = await asyncio.gather(
@@ -687,9 +681,7 @@ async def update_action(
if do_reload:
try:
from app.services import config_file_service
await config_file_service.jail_service.reload_all(socket_path)
await jail_service.reload_all(socket_path)
except Exception as exc: # noqa: BLE001
log.warning(
"reload_after_action_update_failed",
@@ -757,9 +749,7 @@ async def create_action(
if do_reload:
try:
from app.services import config_file_service
await config_file_service.jail_service.reload_all(socket_path)
await jail_service.reload_all(socket_path)
except Exception as exc: # noqa: BLE001
log.warning(
"reload_after_action_create_failed",
@@ -884,9 +874,7 @@ async def assign_action_to_jail(
if do_reload:
try:
from app.services import config_file_service
await config_file_service.jail_service.reload_all(socket_path)
await jail_service.reload_all(socket_path)
except Exception as exc: # noqa: BLE001
log.warning(
"reload_after_assign_action_failed",
@@ -944,9 +932,7 @@ async def remove_action_from_jail(
if do_reload:
try:
from app.services import config_file_service
await config_file_service.jail_service.reload_all(socket_path)
await jail_service.reload_all(socket_path)
except Exception as exc: # noqa: BLE001
log.warning(
"reload_after_remove_action_failed",

File diff suppressed because it is too large Load Diff

View File

@@ -25,8 +25,8 @@ from app.exceptions import (
)
import app.services.jail_service as jail_service
from app.utils.config_file_utils import (
_get_active_jail_names,
_parse_jails_sync,
_get_active_jail_names as _config_file_get_active_jail_names,
_parse_jails_sync as _config_file_parse_jails_sync,
_safe_filter_name,
_safe_jail_name,
set_jail_local_key_sync,
@@ -49,15 +49,11 @@ log: structlog.stdlib.BoundLogger = structlog.get_logger()
# ---------------------------------------------------------------------------
def _parse_jails_sync(config_dir: Path) -> tuple[dict[str, dict[str, str]], Path]:
from app.services import config_file_service
return config_file_service._parse_jails_sync(config_dir)
return _config_file_parse_jails_sync(config_dir)
async def _get_active_jail_names(socket_path: str) -> set[str]:
from app.services import config_file_service
return await config_file_service._get_active_jail_names(socket_path)
return await _config_file_get_active_jail_names(socket_path)
# ---------------------------------------------------------------------------
@@ -84,7 +80,7 @@ def _resolve_filter(raw_filter: str, jail_name: str, mode: str) -> str:
return result
# ---------------------------------------------------------------------------
# Internal helpers - from config_file_service for local use
# Internal helpers imported from the shared config helper module.
# ---------------------------------------------------------------------------
@@ -313,8 +309,6 @@ async def list_filters(
"""
filter_d = Path(config_dir) / "filter.d"
from app.services import config_file_service
# Run the synchronous scan in a thread-pool executor.
raw_filters: list[tuple[str, str, str, bool, str]] = await run_blocking(_parse_filters_sync, filter_d)
@@ -514,9 +508,7 @@ async def update_filter(
if do_reload:
try:
from app.services import config_file_service
await config_file_service.jail_service.reload_all(socket_path)
await jail_service.reload_all(socket_path)
except Exception as exc: # noqa: BLE001
log.warning(
"reload_after_filter_update_failed",
@@ -590,9 +582,7 @@ async def create_filter(
if do_reload:
try:
from app.services import config_file_service
await config_file_service.jail_service.reload_all(socket_path)
await jail_service.reload_all(socket_path)
except Exception as exc: # noqa: BLE001
log.warning(
"reload_after_filter_create_failed",
@@ -686,7 +676,6 @@ async def assign_filter_to_jail(
"""
_safe_jail_name(jail_name)
_safe_filter_name(req.filter_name)
from app.services import config_file_service
_safe_jail_name(jail_name)
@@ -715,9 +704,7 @@ async def assign_filter_to_jail(
if do_reload:
try:
from app.services import config_file_service
await config_file_service.jail_service.reload_all(socket_path)
await jail_service.reload_all(socket_path)
except Exception as exc: # noqa: BLE001
log.warning(
"reload_after_assign_filter_failed",

View File

@@ -28,8 +28,13 @@ from app.exceptions import (
import app.services.jail_service as jail_service
from app.utils.config_file_utils import (
_build_inactive_jail,
_parse_jails_sync as _config_file_parse_jails_sync,
_get_active_jail_names as _config_file_get_active_jail_names,
_probe_fail2ban_running,
_safe_jail_name,
_validate_jail_config_sync as _config_file_validate_jail_config_sync,
start_daemon,
wait_for_fail2ban,
)
from app.models.config import (
ActivateJailRequest,
@@ -47,17 +52,13 @@ 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)
"""Parse jail config files using the shared config helper."""
return _config_file_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)
"""Return the currently active jail names from fail2ban."""
return await _config_file_get_active_jail_names(socket_path)
# ---------------------------------------------------------------------------
@@ -228,8 +229,8 @@ def _validate_regex_patterns(patterns: list[str]) -> None:
raise FilterInvalidRegexError(pattern, str(exc)) from exc
# Shared functions from config_file_service are imported directly from the
# canonical shared helper module.
# Shared functions from the legacy config helper are imported directly from
# the canonical shared helper module.
# ---------------------------------------------------------------------------
@@ -346,10 +347,8 @@ async def _activate_jail(
# ---------------------------------------------------------------------- #
# Pre-activation validation — collect warnings but do not block #
# ---------------------------------------------------------------------- #
from app.services import config_file_service
validation_result: JailValidationResult = await run_blocking(
config_file_service._validate_jail_config_sync,
_config_file_validate_jail_config_sync,
Path(config_dir),
name,
)
@@ -405,9 +404,7 @@ 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])
await jail_service.reload_all(socket_path, include_jails=[name])
except JailNotFoundError as exc:
# Jail configuration is invalid (e.g. missing logpath that prevents
# fail2ban from loading the jail). Roll back and provide a specific error.
@@ -453,7 +450,7 @@ async def _activate_jail(
for attempt in range(_POST_RELOAD_MAX_ATTEMPTS):
if attempt > 0:
await asyncio.sleep(_POST_RELOAD_PROBE_INTERVAL)
if await config_file_service._probe_fail2ban_running(socket_path):
if await _probe_fail2ban_running(socket_path):
fail2ban_running = True
break
@@ -478,7 +475,7 @@ async def _activate_jail(
)
# Verify the jail actually started (config error may prevent it silently).
post_reload_names = await config_file_service._get_active_jail_names(socket_path)
post_reload_names = await _get_active_jail_names(socket_path)
actually_running = name in post_reload_names
if not actually_running:
log.warning(
@@ -549,21 +546,17 @@ 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)
await jail_service.reload_all(socket_path)
log.info("jail_activation_rollback_reload_ok", jail=name)
except Exception as exc: # noqa: BLE001
log.warning("jail_activation_rollback_reload_failed", jail=name, error=str(exc))
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)
if await config_file_service._probe_fail2ban_running(socket_path):
if await _probe_fail2ban_running(socket_path):
log.info("jail_activation_rollback_recovered", jail=name)
return True
@@ -614,16 +607,14 @@ async def _deactivate_jail(
~app.utils.fail2ban_client.Fail2BanConnectionError: If the fail2ban
socket is unreachable during reload.
"""
from app.services import config_file_service
_safe_jail_name(name)
config_file_service.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(_config_file_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 not in active_names:
raise JailAlreadyInactiveError(name)
@@ -635,9 +626,7 @@ 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])
await 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))
@@ -673,16 +662,14 @@ 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
_safe_jail_name(name)
config_file_service.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(_config_file_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)
@@ -715,11 +702,9 @@ 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)
_safe_jail_name(name)
return await run_blocking(
config_file_service._validate_jail_config_sync,
_config_file_validate_jail_config_sync,
Path(config_dir),
name,
)
@@ -777,14 +762,12 @@ 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)
started = await 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(
fail2ban_running = await wait_for_fail2ban(
socket_path,
max_wait_seconds=10.0,
poll_interval=2.0,
@@ -792,7 +775,7 @@ async def _rollback_jail(
active_jails = 0
if fail2ban_running:
names = await config_file_service._get_active_jail_names(socket_path)
names = await _get_active_jail_names(socket_path)
active_jails = len(names)
if fail2ban_running:
log.info("jail_rollback_success", jail=name, active_jails=active_jails)