feat (volumes): voeg volumes router toe aan backend

Nieuw bestand control/app_volumes.py met Libpod volume operaties:
- GET  /volumes          — lijst alle volumes (optioneel ?filters=key=value)
- POST /volumes          — volume aanmaken (name, driver, labels, driverOpts)
- GET  /volumes/{name}   — details van één volume
- GET  /volumes/{name}/exists — bestaanskontrolle (204 → true, 404 → false)
- DELETE /volumes/{name} — volume verwijderen (?force=true optioneel)
- POST /volumes/prune    — ⚠️ verwijdert alle ongebruikte volumes

Filters: key=value formaat wordt automatisch omgezet naar
{"key":["value"]} JSON dat de Libpod API verwacht.

Containerfile: COPY app_volumes.py toegevoegd.
app.py: init_volumes_router geregistreerd.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 13:25:58 +01:00
parent 4404c02967
commit f8bbb783b0
3 changed files with 99 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
from __future__ import annotations
import json
from typing import Dict, Optional
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
def _normalize_filters(filters: str) -> str:
"""Zet key=value formaat om naar {"key":["value"]} JSON dat Libpod verwacht.
Als de waarde al met '{' begint, wordt hij ongewijzigd doorgegeven."""
if filters.startswith("{"):
return filters
# key=value → {"key": ["value"]}
if "=" in filters:
key, _, value = filters.partition("=")
return json.dumps({key.strip(): [value.strip()]})
# Alleen een key zonder waarde → {"key": ["true"]}
return json.dumps({filters.strip(): ["true"]})
class VolumeCreateRequest(BaseModel):
name: str
driver: str = "local"
driverOpts: Optional[Dict[str, str]] = None
labels: Optional[Dict[str, str]] = None
def _raise_on_error(resp):
if 200 <= resp.status_code < 300:
return
raise HTTPException(status_code=resp.status_code, detail=resp.text)
def init_volumes_router(session, podman_api_base: str) -> APIRouter:
router = APIRouter(prefix="/volumes", tags=["volumes"])
@router.get("")
def list_volumes(filters: Optional[str] = Query(None)):
url = f"{podman_api_base}/libpod/volumes/json"
params = {}
if filters is not None:
params["filters"] = _normalize_filters(filters)
resp = session.get(url, params=params)
_raise_on_error(resp)
return resp.json()
@router.post("")
def create_volume(req: VolumeCreateRequest):
url = f"{podman_api_base}/libpod/volumes/create"
body: dict = {"name": req.name, "driver": req.driver}
if req.driverOpts:
body["driverOpts"] = req.driverOpts
if req.labels:
body["labels"] = req.labels
resp = session.post(url, json=body)
_raise_on_error(resp)
return resp.json()
@router.post("/prune")
def prune_volumes():
"""⚠️ Destructief: verwijdert alle ongebruikte volumes permanent. Niet terug te draaien."""
url = f"{podman_api_base}/libpod/volumes/prune"
resp = session.post(url)
_raise_on_error(resp)
return resp.json()
@router.get("/{name}/exists")
def volume_exists(name: str):
url = f"{podman_api_base}/libpod/volumes/{name}/exists"
resp = session.get(url)
if resp.status_code == 204:
return {"exists": True}
if resp.status_code == 404:
return {"exists": False}
_raise_on_error(resp)
@router.get("/{name}")
def get_volume(name: str):
url = f"{podman_api_base}/libpod/volumes/{name}/json"
resp = session.get(url)
_raise_on_error(resp)
return resp.json()
@router.delete("/{name}")
def remove_volume(name: str, force: bool = Query(False)):
"""⚠️ Destructief: verwijdert een volume permanent. Niet terug te draaien als het volume data bevat."""
url = f"{podman_api_base}/libpod/volumes/{name}"
params = {"force": str(force).lower()}
resp = session.delete(url, params=params)
if resp.status_code == 204:
return {"ok": True}
_raise_on_error(resp)
return router