Add filter write/create/delete and jail-filter assign endpoints (Task 2.2)

- PUT /api/config/filters/{name}: updates failregex/ignoreregex/datepattern/
  journalmatch in filter.d/{name}.local; validates regex via re.compile();
  supports ?reload=true
- POST /api/config/filters: creates filter.d/{name}.local from FilterCreateRequest;
  returns 409 if file already exists
- DELETE /api/config/filters/{name}: deletes .local only; returns 409 for
  conf-only (readonly) filters
- POST /api/config/jails/{name}/filter: assigns filter to jail by writing
  'filter = {name}' to jail.d/{jail}.local; supports ?reload=true
- New models: FilterUpdateRequest, FilterCreateRequest, AssignFilterRequest
- New service helpers: _safe_filter_name, _validate_regex_patterns,
  _write_filter_local_sync, _set_jail_local_key_sync
- Fixed .local-only filter discovery in _parse_filters_sync (5-tuple return)
- Fixed get_filter extension stripping (.local is 6 chars not 5)
- Renamed file_config.py raw-write routes to /raw suffix
  (PUT /filters/{name}/raw, POST /filters/raw) to avoid routing conflicts
- Full service + router tests; all 930 tests pass
This commit is contained in:
2026-03-13 18:13:03 +01:00
parent 4c138424a5
commit e15ad8fb62
8 changed files with 1885 additions and 64 deletions

View File

@@ -262,7 +262,7 @@ class TestUpdateFilterFile:
AsyncMock(return_value=None),
):
resp = await file_config_client.put(
"/api/config/filters/nginx",
"/api/config/filters/nginx/raw",
json={"content": "[Definition]\nfailregex = test\n"},
)
@@ -274,7 +274,7 @@ class TestUpdateFilterFile:
AsyncMock(side_effect=ConfigFileWriteError("disk full")),
):
resp = await file_config_client.put(
"/api/config/filters/nginx",
"/api/config/filters/nginx/raw",
json={"content": "x"},
)
@@ -293,7 +293,7 @@ class TestCreateFilterFile:
AsyncMock(return_value="myfilter.conf"),
):
resp = await file_config_client.post(
"/api/config/filters",
"/api/config/filters/raw",
json={"name": "myfilter", "content": "[Definition]\n"},
)
@@ -306,7 +306,7 @@ class TestCreateFilterFile:
AsyncMock(side_effect=ConfigFileExistsError("myfilter.conf")),
):
resp = await file_config_client.post(
"/api/config/filters",
"/api/config/filters/raw",
json={"name": "myfilter", "content": "[Definition]\n"},
)
@@ -318,7 +318,7 @@ class TestCreateFilterFile:
AsyncMock(side_effect=ConfigFileNameError("bad/../name")),
):
resp = await file_config_client.post(
"/api/config/filters",
"/api/config/filters/raw",
json={"name": "../escape", "content": "[Definition]\n"},
)