Initial commit - podman-mvp net na toevoegen cpu en mem kolommen

This commit is contained in:
kodi
2026-02-18 08:17:27 +01:00
commit 62e195c59e
56 changed files with 22164 additions and 0 deletions
+238
View File
@@ -0,0 +1,238 @@
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):
pod_name = f"pod{name}"
if action == "stop":
# Probeer de hele pod te stoppen
res = SESSION.post(f"{PODMAN_API_BASE}/libpod/pods/{pod_name}/stop")
if res.status_code == 204 or res.status_code == 200:
return {"status": "stopped", "target": pod_name}
# Fallback: als het geen pod is, stop individuele containers
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]]
for c in matching:
SESSION.post(f"{PODMAN_API_BASE}/libpod/containers/{c['Id']}/stop")
return {"status": "stop-attempted", "count": len(matching)}
if action == "start":
# Altijd eerst geforceerd de oude pod verwijderen
SESSION.delete(f"{PODMAN_API_BASE}/libpod/pods/{pod_name}?force=true")
target_path = None
for root, dirs, files in os.walk(WORKLOADS_DIR):
for f in files:
if f.startswith(name) and f.endswith('.yaml'):
target_path = os.path.join(root, f)
break
if target_path:
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()
return {"status": "unknown_action"}
# --- 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"
}
@app.get("/api/files/tree")
def get_file_tree():
"""Geeft een lijst van alle mappen en bestanden in de workloads map."""
tree = []
for root, dirs, files in os.walk(WORKLOADS_DIR):
relative_path = os.relpath(root, WORKLOADS_DIR)
if relative_path == ".":
relative_path = ""
tree.append({
"path": relative_path,
"dirs": dirs,
"files": [f for f in files if f.endswith(('.yaml', '.container'))]
})
return tree
@app.get("/api/files/read")
def read_file(path: str):
"""Leest de inhoud van een specifiek bestand op."""
full_path = os.path.normpath(os.path.join(WORKLOADS_DIR, path))
if not full_path.startswith(str(WORKLOADS_DIR)):
raise HTTPException(status_code=403, detail="Toegang geweigerd")
if not os.path.exists(full_path):
raise HTTPException(status_code=404, detail="Bestand niet gevonden")
with open(full_path, 'r') as f:
return {"content": f.read()}
@app.post("/api/files/save")
def save_file(path: str, data: FileContent):
"""Slaat de inhoud op naar een bestand (overschrijft bestaand)."""
full_path = os.path.normpath(os.path.join(WORKLOADS_DIR, path))
if not full_path.startswith(str(WORKLOADS_DIR)):
raise HTTPException(status_code=403, detail="Toegang geweigerd")
# Maak mappen aan als ze niet bestaan
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'w') as f:
f.write(data.content)
return {"status": "success", "path": path}
@app.delete("/api/files/delete")
def delete_file(path: str):
"""Verwijdert een bestand."""
full_path = os.path.normpath(os.path.join(WORKLOADS_DIR, path))
if not full_path.startswith(str(WORKLOADS_DIR)):
raise HTTPException(status_code=403, detail="Toegang geweigerd")
if os.path.exists(full_path):
os.remove(full_path)
return {"status": "deleted"}
raise HTTPException(status_code=404, detail="Bestand niet gevonden")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)