Files
DECNET/decnet/web/router/topology/api_reap_orphans.py
anti df84981954 feat(api): pin response_model on dict-returning mutation routes
Every mutation route that returned an untyped dict now declares
response_model at the decorator. MessageResponse covers the eight
{"message": ...} envelopes (change-password, mutate-decky, mutate-
interval, update-deployment-limit, update-global-mutation-interval,
delete-user, update-user-role, reset-user-password). Purpose-built
models cover the richer shapes (DeployResponse for /deckies/deploy,
PurgeResponse for /config/reinit, ReapReportResponse for /reap-orphans,
UserResponse for /config/users). 204-No-Content and Response/
ORJSONResponse routes stay as-is.

The wire shape for clients is unchanged — the envelopes already only
shipped a message field. What changes is that a handler which
accidentally returns a richer dict (e.g. a full user row including
password_hash) would be silently stripped to the declared fields at
serialization time.

Also flips F4/D "expensive LIKE" to accepted (new DA-09) — the /logs
and /attackers search routes LIKE-scan unbounded columns, but both are
admin-gated, limit-capped, and operator rate-limit scope per DA-04.
FTS5 stays a performance TODO, not a security blocker.
2026-04-24 14:27:58 -04:00

49 lines
1.7 KiB
Python

"""POST /topologies/reap-orphans — remove Docker resources for topology
ids the DB no longer knows about.
A topology row deleted outside the teardown flow (operator error,
crashed master, direct DB edit) leaves its containers and bridge
networks behind. The orphan networks keep their IPAM pools, so the
next deploy at the same subnet hits a 403 ``Pool overlaps`` from the
Docker daemon.
This endpoint walks the local Docker daemon, computes the set of
topology prefixes still known to the repo, and force-removes every
container + network whose prefix is orphaned. Resources belonging to
live topologies are never touched.
"""
from __future__ import annotations
from fastapi import APIRouter, Depends
from decnet.engine.reaper import reap_orphan_topology_resources
from decnet.telemetry import traced as _traced
from decnet.web.db.models import ReapReportResponse
from decnet.web.dependencies import repo, require_admin
router = APIRouter()
@router.post(
"/reap-orphans",
tags=["MazeNET Topologies"],
response_model=ReapReportResponse,
responses={
401: {"description": "Missing or invalid credentials"},
403: {"description": "Insufficient permissions"},
},
)
@_traced("api.topology.reap_orphans")
async def api_reap_orphans(
_admin: dict = Depends(require_admin),
) -> dict:
"""Reap Docker resources whose topology id is absent from the DB.
Returns a report with the live prefixes, the orphan prefixes that
were identified, every container + network actually removed, and
any per-resource errors encountered. Errors are non-fatal — a
single stuck resource does not abort the sweep.
"""
report = await reap_orphan_topology_resources(repo)
return report.to_dict()