feat(06-02): add snapshot view and diff retrieval endpoints
- GET /config/{snapshot_id} returns decrypted full config with RBAC
- GET /config/{snapshot_id}/diff returns unified diff text with RBAC
- 404 for missing snapshots/diffs, 500 for Transit decrypt failure
- Both endpoints enforce viewer+ role and config:read scope
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.middleware.rbac import require_min_role, require_scope
|
from app.middleware.rbac import require_min_role, require_scope
|
||||||
from app.middleware.tenant_context import CurrentUser, get_current_user
|
from app.middleware.tenant_context import CurrentUser, get_current_user
|
||||||
from app.services.config_history_service import get_config_history
|
from app.services.config_history_service import (
|
||||||
|
get_config_history,
|
||||||
|
get_snapshot,
|
||||||
|
get_snapshot_diff,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -81,3 +85,83 @@ async def list_config_history(
|
|||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/tenants/{tenant_id}/devices/{device_id}/config/{snapshot_id}",
|
||||||
|
summary="Get decrypted config snapshot",
|
||||||
|
dependencies=[require_scope("config:read")],
|
||||||
|
)
|
||||||
|
async def view_snapshot(
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
device_id: uuid.UUID,
|
||||||
|
snapshot_id: uuid.UUID,
|
||||||
|
current_user: CurrentUser = Depends(get_current_user),
|
||||||
|
_role: CurrentUser = Depends(require_min_role("viewer")),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Return the decrypted full config text for a single snapshot.
|
||||||
|
|
||||||
|
Returns 404 if the snapshot does not exist or belongs to a different
|
||||||
|
device/tenant.
|
||||||
|
"""
|
||||||
|
await _check_tenant_access(current_user, tenant_id, db)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await get_snapshot(
|
||||||
|
snapshot_id=str(snapshot_id),
|
||||||
|
device_id=str(device_id),
|
||||||
|
tenant_id=str(tenant_id),
|
||||||
|
session=db,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to decrypt snapshot %s for device %s", snapshot_id, device_id
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail="Failed to decrypt snapshot content",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Snapshot not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/tenants/{tenant_id}/devices/{device_id}/config/{snapshot_id}/diff",
|
||||||
|
summary="Get diff for a config snapshot",
|
||||||
|
dependencies=[require_scope("config:read")],
|
||||||
|
)
|
||||||
|
async def view_snapshot_diff(
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
device_id: uuid.UUID,
|
||||||
|
snapshot_id: uuid.UUID,
|
||||||
|
current_user: CurrentUser = Depends(get_current_user),
|
||||||
|
_role: CurrentUser = Depends(require_min_role("viewer")),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Return the unified diff associated with a snapshot.
|
||||||
|
|
||||||
|
Returns 404 if no diff exists (e.g., first snapshot for a device).
|
||||||
|
"""
|
||||||
|
await _check_tenant_access(current_user, tenant_id, db)
|
||||||
|
|
||||||
|
result = await get_snapshot_diff(
|
||||||
|
snapshot_id=str(snapshot_id),
|
||||||
|
device_id=str(device_id),
|
||||||
|
tenant_id=str(tenant_id),
|
||||||
|
session=db,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="No diff found for this snapshot",
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user