diff --git a/control/Containerfile b/control/Containerfile index 5040ee5..e31dd8a 100644 --- a/control/Containerfile +++ b/control/Containerfile @@ -4,6 +4,7 @@ RUN apt-get update && apt-get install -y curl systemd && rm -rf /var/lib/apt/lis RUN pip install fastapi uvicorn requests-unixsocket pyyaml pytest httpx COPY app.py . COPY app_images.py . +COPY app_volumes.py . COPY app_files.py . COPY app_networks.py . COPY app_pods.py . diff --git a/control/app.py b/control/app.py index b1ffdaf..71aa554 100644 --- a/control/app.py +++ b/control/app.py @@ -1,4 +1,5 @@ from app_images import init_images_router +from app_volumes import init_volumes_router from app_files import init_files_router from app_pods import init_pods_router from app_containers import init_containers_router, start_stats_poller @@ -27,6 +28,7 @@ def _systemctl(cmd): # --- ROUTERS --- # Images API lives in dedicated modules to keep this file from growing further. app.include_router(init_images_router(SESSION, PODMAN_API_BASE)) +app.include_router(init_volumes_router(SESSION, PODMAN_API_BASE)) app.include_router(init_files_router(SESSION, PODMAN_API_BASE, WORKLOADS_DIR)) app.include_router(init_networks_router(SESSION, PODMAN_API_BASE)) app.include_router(init_containers_router( diff --git a/control/app_volumes.py b/control/app_volumes.py new file mode 100644 index 0000000..e0c479c --- /dev/null +++ b/control/app_volumes.py @@ -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