import os from fastapi import FastAPI, HTTPException from pydantic import BaseModel import requests_unixsocket import uvicorn app = FastAPI(title="Podman MVP Control Plane", root_path="/api") SESSION = requests_unixsocket.Session() PODMAN_API_BASE = "http+unix://%2Frun%2Fuser%2F1000%2Fpodman%2Fpodman.sock/v5.4.2" WORKLOADS_DIR = "/app/workloads" class FileCreate(BaseModel): path: str content: str @app.get("/workloads") def list_workloads(): yaml_files = [] for root, dirs, files in os.walk(WORKLOADS_DIR): for file in files: if file.endswith((".yaml", ".kube")): rel_path = os.path.relpath(os.path.join(root, file), WORKLOADS_DIR) yaml_files.append(rel_path) return {"workloads": sorted(yaml_files)} @app.get("/workloads/read/{filename:path}") def read_workload(filename: str): path = os.path.join(WORKLOADS_DIR, filename) if not os.path.exists(path): raise HTTPException(status_code=404) with open(path, 'r') as f: return {"filename": filename, "content": f.read()} @app.post("/workloads/save-file") def save_file(file_data: FileCreate): target_path = os.path.join(WORKLOADS_DIR, file_data.path) os.makedirs(os.path.dirname(target_path), exist_ok=True) with open(target_path, 'w') as f: f.write(file_data.content) return {"status": "success"} @app.post("/workloads/deploy/{filename:path}") def deploy_workload(filename: str): path = os.path.join(WORKLOADS_DIR, filename) with open(path, 'r') as f: yaml_content = f.read() url = f"{PODMAN_API_BASE}/libpod/kube/play" return SESSION.post(url, data=yaml_content).json() # --- PODS --- @app.get("/pods") def list_pods(): # Cruciaal: ?all=true zorgt dat EXIT_STATE pods ook getoond worden url = f"{PODMAN_API_BASE}/libpod/pods/json?all=true" return SESSION.get(url).json() @app.post("/actions/{action}/{name}") def take_action(action: str, name: str): # 1. Haal alle containers op api_containers = SESSION.get(f"{PODMAN_API_BASE}/libpod/containers/json?all=true").json() matching = [c for c in api_containers if name in c['Names'][0]] # 2. Zoek het bestand op schijf om te bepalen wat voor type het is target_path = None file_ext = "" for root, dirs, files in os.walk(WORKLOADS_DIR): for f in files: if f.startswith(name): target_path = os.path.join(root, f) file_ext = f.split('.')[-1] break if action == "stop": # Bij stop: probeer container te stoppen als die er is if matching: for c in matching: SESSION.post(f"{PODMAN_API_BASE}/libpod/containers/{c['Id']}/stop") return {"status": "stopped", "count": len(matching)} return {"status": "already stopped or not found"} if action == "start": # TYPE 1: YAML (Kube Play) if file_ext == "yaml": with open(target_path, 'r') as file: yaml_content = file.read() url = f"{PODMAN_API_BASE}/libpod/kube/play" res = SESSION.post(url, data=yaml_content) return res.json() # TYPE 2: QUADLET (.container) # Omdat Quadlets via systemd gaan en de container weg is, # is de enige 'schone' weg via de API een 'prune' en herstart triggeren # Voor nu proberen we de container her-aanmaak te forceren: if file_ext == "container": # Bij Quadlets op Debian is het vaak het beste om een 'generate' # of een specifieke systeem-trigger te doen. # TEST: We sturen een bericht dat we dit via een shell-commando moeten doen return {"status": "error", "message": "Quadlets (.container) vereisen systemctl start op de host."} return {"status": "unknown"} # --- CONTAINERS --- @app.get("/containers") def list_containers(): # Ook hier ?all=true voor gestopte containers url = f"{PODMAN_API_BASE}/libpod/containers/json?all=true" return SESSION.get(url).json() @app.post("/containers/{action}/{name}") def container_action(action: str, name: str): # Podman API pad: /libpod/containers/{name}/{action} url = f"{PODMAN_API_BASE}/libpod/containers/{name}/{action}" res = SESSION.post(url) return {"status": "success", "code": res.status_code} @app.get("/dashboard") def get_dashboard(): try: api_containers = SESSION.get(f"{PODMAN_API_BASE}/libpod/containers/json?all=true").json() except: api_containers = [] dashboard = {} for root, dirs, files in os.walk(WORKLOADS_DIR): for f in files: if f.endswith(('.yaml', '.kube', '.container')): rel_path = os.path.relpath(os.path.join(root, f), WORKLOADS_DIR) # Gebruik de bestandsnaam zonder extensie als unieke sleutel name_base = f.split('.')[0] # Als we mediaserver.yaml EN mediaserver.kube hebben, zien we ze als 1 item if name_base not in dashboard: # Zoek containers die horen bij dit bestand (fuzzy match op naam) matching = [c for c in api_containers if name_base in c['Names'][0]] status = "stopped" ip = "-" container_count = len(matching) if matching: # Als er minstens één container draait, is de status 'running' status = "running" if any(c['State'] == 'running' for c in matching) else "exited" # Pak IP van de eerste container die een IP heeft for c in matching: nets = c.get('Networks', {}) first_net = next(iter(nets.values()), {}) if nets else {} if first_net.get('IPAddress'): ip = first_net['IPAddress'] break dashboard[name_base] = { "name": name_base, "path": rel_path, "status": status, "ip": ip, "containers": container_count } return list(dashboard.values()) @app.post("/actions/{action}/{name}") def take_action(action: str, name: str): """Starten/Stoppen via de API.""" # Voor Quadlets/Kube gebruiken we 'play' om te starten en 'stop' op de container om te stoppen if action == "start": # Hier zouden we 'podman kube play' logica doen pass else: url = f"{PODMAN_API_BASE}/libpod/containers/{name}/stop" SESSION.post(url) return {"status": "request sent"} @app.get("/test-hybrid") def test_hybrid(): # 1. Check bestanden op schijf try: files = os.listdir(WORKLOADS_DIR) except Exception as e: return {"error": f"Kan map {WORKLOADS_DIR} niet lezen: {str(e)}"} # 2. Check Podman API try: api_res = SESSION.get(f"{PODMAN_API_BASE}/libpod/containers/json?all=true") api_containers = api_res.json() except Exception as e: api_containers = f"API Fout: {str(e)}" return { "bestanden_gevonden": files, "api_containers_aantal": len(api_containers) if isinstance(api_containers, list) else 0, "api_raw_sample": api_containers[0] if isinstance(api_containers, list) and len(api_containers) > 0 else "Geen containers" } if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)