feat: action config service, router endpoints, and full test coverage (Tasks 3.1, 3.2, 3.4)
- ActionConfig extended with active, used_by_jails, source_file, has_local_override
- New models: ActionListResponse, ActionUpdateRequest, ActionCreateRequest, AssignActionRequest
- New service functions: list_actions, get_action, update_action, create_action, delete_action, assign_action_to_jail, remove_action_from_jail
- New error classes: ActionNotFoundError, ActionAlreadyExistsError, ActionReadonlyError, ActionNameError
- New router endpoints: GET/PUT/POST/DELETE /api/config/actions, POST/DELETE /api/config/jails/{name}/action
- Service + router tests: 290 tests passing, mypy strict clean, ruff clean
This commit is contained in:
@@ -508,6 +508,33 @@ 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 /
|
||||
# get_action; default to safe "inactive" values when not computed.
|
||||
active: bool = Field(
|
||||
default=False,
|
||||
description=(
|
||||
"``True`` when this action is referenced by at least one currently "
|
||||
"enabled (running) jail."
|
||||
),
|
||||
)
|
||||
used_by_jails: list[str] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"Names of currently enabled jails that reference this action. "
|
||||
"Empty when ``active`` is ``False``."
|
||||
),
|
||||
)
|
||||
source_file: str = Field(
|
||||
default="",
|
||||
description="Absolute path to the ``.conf`` source file for this action.",
|
||||
)
|
||||
has_local_override: bool = Field(
|
||||
default=False,
|
||||
description=(
|
||||
"``True`` when a ``.local`` override file exists alongside the "
|
||||
"base ``.conf`` file."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ActionConfigUpdate(BaseModel):
|
||||
@@ -527,6 +554,110 @@ class ActionConfigUpdate(BaseModel):
|
||||
init_vars: dict[str, str] | None = Field(default=None)
|
||||
|
||||
|
||||
class ActionListResponse(BaseModel):
|
||||
"""Response for ``GET /api/config/actions``."""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
actions: list[ActionConfig] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"All discovered actions, each annotated with active/inactive status "
|
||||
"and the jails that reference them."
|
||||
),
|
||||
)
|
||||
total: int = Field(..., ge=0, description="Total number of actions found.")
|
||||
|
||||
|
||||
class ActionUpdateRequest(BaseModel):
|
||||
"""Payload for ``PUT /api/config/actions/{name}``.
|
||||
|
||||
Accepts only the user-editable ``[Definition]`` lifecycle fields and
|
||||
``[Init]`` parameters. Fields left as ``None`` are not changed.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
actionstart: str | None = Field(
|
||||
default=None,
|
||||
description="Updated ``actionstart`` command. ``None`` = keep existing.",
|
||||
)
|
||||
actionstop: str | None = Field(
|
||||
default=None,
|
||||
description="Updated ``actionstop`` command. ``None`` = keep existing.",
|
||||
)
|
||||
actioncheck: str | None = Field(
|
||||
default=None,
|
||||
description="Updated ``actioncheck`` command. ``None`` = keep existing.",
|
||||
)
|
||||
actionban: str | None = Field(
|
||||
default=None,
|
||||
description="Updated ``actionban`` command. ``None`` = keep existing.",
|
||||
)
|
||||
actionunban: str | None = Field(
|
||||
default=None,
|
||||
description="Updated ``actionunban`` command. ``None`` = keep existing.",
|
||||
)
|
||||
actionflush: str | None = Field(
|
||||
default=None,
|
||||
description="Updated ``actionflush`` command. ``None`` = keep existing.",
|
||||
)
|
||||
definition_vars: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
description="Additional ``[Definition]`` variables to set. ``None`` = keep existing.",
|
||||
)
|
||||
init_vars: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
description="``[Init]`` parameters to set. ``None`` = keep existing.",
|
||||
)
|
||||
|
||||
|
||||
class ActionCreateRequest(BaseModel):
|
||||
"""Payload for ``POST /api/config/actions``.
|
||||
|
||||
Creates a new user-defined action at ``action.d/{name}.local``.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
name: str = Field(
|
||||
...,
|
||||
description="Action base name (e.g. ``my-custom-action``). Must not already exist.",
|
||||
)
|
||||
actionstart: str | None = Field(default=None, description="Command to execute at jail start.")
|
||||
actionstop: str | None = Field(default=None, description="Command to execute at jail stop.")
|
||||
actioncheck: str | None = Field(default=None, description="Command to execute before each ban.")
|
||||
actionban: str | None = Field(default=None, description="Command to execute to ban an IP.")
|
||||
actionunban: str | None = Field(default=None, description="Command to execute to unban an IP.")
|
||||
actionflush: str | None = Field(default=None, description="Command to flush all bans on shutdown.")
|
||||
definition_vars: dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
description="Additional ``[Definition]`` variables.",
|
||||
)
|
||||
init_vars: dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
description="``[Init]`` runtime parameters.",
|
||||
)
|
||||
|
||||
|
||||
class AssignActionRequest(BaseModel):
|
||||
"""Payload for ``POST /api/config/jails/{jail_name}/action``."""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
action_name: str = Field(
|
||||
...,
|
||||
description="Action base name to add to the jail (e.g. ``iptables-multiport``).",
|
||||
)
|
||||
params: dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
description=(
|
||||
"Optional per-jail action parameters written as "
|
||||
"``action_name[key=value, ...]`` in the jail config."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Jail file config models (Task 6.1)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user