refactor(api): move pods endpoints into app_pods router
This commit is contained in:
+16
-147
@@ -3,6 +3,7 @@ import sys
|
||||
import subprocess
|
||||
from app_images import init_images_router
|
||||
from app_files import init_files_router
|
||||
from app_pods import init_pods_router
|
||||
from app_networks import init_networks_router
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
@@ -166,62 +167,6 @@ def health():
|
||||
return {"ok": ok, "podman": {"ok": podman_ok}, "systemd_user": {"reachable": systemd_reachable}}
|
||||
|
||||
|
||||
# --- PODS / CONTAINERS ---
|
||||
@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 _podman_get_json(url)
|
||||
|
||||
|
||||
@app.post("/actions/{action}/{name}")
|
||||
def take_action(action: str, name: str):
|
||||
# Legacy endpoint (keep behavior)
|
||||
possible_names = [name, f"pod{name}", f"pod-{name}"]
|
||||
|
||||
if action == "start":
|
||||
# STAP 1: Probeer direct de pod te starten (de 'Cockpit' methode)
|
||||
for target in possible_names:
|
||||
res = _podman_post(f"{PODMAN_API_BASE}/libpod/pods/{target}/start")
|
||||
if res.status_code in (200, 204):
|
||||
return {"status": "started", "target": target, "method": "direct"}
|
||||
|
||||
# STAP 2: Als direct starten faalt, probeer dan YAML opnieuw te deployen
|
||||
target_path = None
|
||||
for ext in (".yaml", ".yml"):
|
||||
cand = os.path.join(WORKLOADS_DIR, f"{name}{ext}")
|
||||
if os.path.exists(cand):
|
||||
target_path = cand
|
||||
break
|
||||
|
||||
if target_path:
|
||||
with open(target_path, 'r') as file:
|
||||
yaml_content = file.read()
|
||||
res = _podman_post(f"{PODMAN_API_BASE}/libpod/kube/play", data=yaml_content)
|
||||
|
||||
# SPECIALE CASE: Pod bestaat al, forceer dan restart
|
||||
if res.status_code == 500 and "already exists" in res.text:
|
||||
print(f"DEBUG: Forceer herstart voor {name} wegens conflict")
|
||||
for target in possible_names:
|
||||
_podman_delete(f"{PODMAN_API_BASE}/libpod/pods/{target}?force=true")
|
||||
# Probeer het nu opnieuw
|
||||
retry_res = _podman_post(f"{PODMAN_API_BASE}/libpod/kube/play", data=yaml_content)
|
||||
return retry_res.json()
|
||||
|
||||
return res.json()
|
||||
|
||||
return {"status": "unknown", "method": "no_yaml_found"}
|
||||
|
||||
if action == "stop":
|
||||
for target in possible_names:
|
||||
res = _podman_post(f"{PODMAN_API_BASE}/libpod/pods/{target}/stop")
|
||||
if res.status_code in (200, 204):
|
||||
return {"status": "stopped", "target": target}
|
||||
return {"status": "not found"}
|
||||
|
||||
return {"status": "unknown"}
|
||||
|
||||
|
||||
# --- DASHBOARD HELPERS (contract-neutral, no ordering/sorting changes) ---
|
||||
|
||||
def _build_pod_to_containers_map(containers: list):
|
||||
@@ -247,42 +192,6 @@ def _map_pod_to_unit(podname: str) -> str | None:
|
||||
return f"{podname}.service"
|
||||
|
||||
|
||||
def _append_podman_pods_dashboard_rows(dashboard: list, api_pods: list, pod_to_containers: dict):
|
||||
# preserves original api_pods iteration order
|
||||
for p in api_pods:
|
||||
name = p.get("Name")
|
||||
status = p.get("Status", "unknown")
|
||||
unit = _map_pod_to_unit(name) if name else ""
|
||||
dashboard.append({
|
||||
"Name": name,
|
||||
"Status": status,
|
||||
"Containers": pod_to_containers.get(name, []),
|
||||
"Unit": unit,
|
||||
"Source": "podman",
|
||||
})
|
||||
|
||||
|
||||
def _append_defined_pods_dashboard_rows(dashboard: list, by_name: dict, root_dir: str):
|
||||
# preserves original os.walk order and file iteration order
|
||||
for root, _, files in os.walk(root_dir):
|
||||
for f in files:
|
||||
if f.endswith((".yaml", ".yml")):
|
||||
base = os.path.splitext(os.path.basename(f))[0]
|
||||
pod_name = f"pod{base}"
|
||||
unit_name = _map_pod_to_unit(pod_name)
|
||||
|
||||
if pod_name not in by_name:
|
||||
code, out = _systemctl(["systemctl", "--user", "is-active", unit_name])
|
||||
status = (out or "").strip() or ("active" if code == 0 else "inactive")
|
||||
dashboard.append({
|
||||
"Name": pod_name,
|
||||
"Status": status,
|
||||
"Containers": [],
|
||||
"Unit": unit_name,
|
||||
"Source": "systemd",
|
||||
})
|
||||
|
||||
|
||||
def _ensure_container_status_field(container: dict):
|
||||
# keep exact existing defaulting behavior
|
||||
if "Status" not in container:
|
||||
@@ -318,27 +227,6 @@ def _legacy_dashboard_item_from_container(c: dict):
|
||||
}
|
||||
|
||||
|
||||
@app.get("/pods-dashboard")
|
||||
def pods_dashboard():
|
||||
dashboard = []
|
||||
|
||||
# 0) Bouw mapping: pod_name -> [container_names...]
|
||||
containers = _podman_get_json(f"{PODMAN_API_BASE}/libpod/containers/json?all=true")
|
||||
pod_to_containers = _build_pod_to_containers_map(containers)
|
||||
|
||||
# 1) A) echte pods
|
||||
api_pods = _podman_get_json(f"{PODMAN_API_BASE}/libpod/pods/json?all=true")
|
||||
by_name = {p.get("Name"): p for p in api_pods}
|
||||
|
||||
_append_podman_pods_dashboard_rows(dashboard, api_pods, pod_to_containers)
|
||||
|
||||
# 1) B) defined pods via workloads scan
|
||||
# Based on YAML files in WORKLOADS_DIR; show even if not running.
|
||||
_append_defined_pods_dashboard_rows(dashboard, by_name, WORKLOADS_DIR)
|
||||
|
||||
return dashboard
|
||||
|
||||
|
||||
def _systemd_then_podman(systemd_callable, podman_callable):
|
||||
systemd_res = systemd_callable()
|
||||
if systemd_res is not None:
|
||||
@@ -348,40 +236,6 @@ def _systemd_then_podman(systemd_callable, podman_callable):
|
||||
return podman_callable(None)
|
||||
|
||||
|
||||
def try_systemd_pod_action(action: str, podname: str):
|
||||
# If systemd unit exists/allowed, prefer it.
|
||||
unit = _map_pod_to_unit(podname)
|
||||
if not unit:
|
||||
return None
|
||||
code, out = _systemctl(["systemctl", "--user", action, unit])
|
||||
return {
|
||||
"method": "systemd",
|
||||
"pod": podname,
|
||||
"unit": unit,
|
||||
"cmd": f"systemctl --user {action} {unit}",
|
||||
"exit": code,
|
||||
"output": out,
|
||||
}
|
||||
|
||||
|
||||
@app.post("/pods/actions/{action}/{podname}")
|
||||
def pod_action_prefer_systemd(action: str, podname: str):
|
||||
if action not in ("start", "stop", "restart"):
|
||||
return {"error": "Invalid action"}, 400
|
||||
|
||||
def _systemd_call():
|
||||
return try_systemd_pod_action(action, podname)
|
||||
|
||||
def _podman_call(systemd_res):
|
||||
if systemd_res:
|
||||
note = "systemd failed; falling back to podman"
|
||||
podman = _podman_action_post("pods", podname, action).json()
|
||||
return {"method": "systemd_then_podman", "note": note, "systemd": systemd_res, "podman": podman}
|
||||
return {"method": "podman", "result": _podman_action_post("pods", podname, action).json()}
|
||||
|
||||
return _systemd_then_podman(_systemd_call, _podman_call)
|
||||
|
||||
|
||||
def find_defined_containers():
|
||||
defined = {}
|
||||
for root, _, files in os.walk(os.path.join(WORKLOADS_DIR, "systemd")):
|
||||
@@ -625,6 +479,21 @@ def api_daemon_reload():
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
app.include_router(init_pods_router(
|
||||
SESSION,
|
||||
PODMAN_API_BASE,
|
||||
WORKLOADS_DIR,
|
||||
_podman_get_json,
|
||||
_podman_post,
|
||||
_podman_delete,
|
||||
_systemctl,
|
||||
_podman_action_post,
|
||||
_map_pod_to_unit,
|
||||
_systemd_then_podman,
|
||||
_build_pod_to_containers_map,
|
||||
))
|
||||
|
||||
|
||||
@app.post("/{action}/{unit}")
|
||||
def api_action(action: str, unit: str):
|
||||
if action not in ("status", "start", "stop", "restart"):
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
|
||||
def init_pods_router(
|
||||
session,
|
||||
podman_api_base: str,
|
||||
workloads_dir: str,
|
||||
podman_get_json,
|
||||
podman_post,
|
||||
podman_delete,
|
||||
systemctl_func,
|
||||
podman_action_post,
|
||||
map_pod_to_unit,
|
||||
systemd_then_podman,
|
||||
build_pod_to_containers_map,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(tags=["pods"])
|
||||
|
||||
def _append_podman_pods_dashboard_rows(dashboard: list, api_pods: list, pod_to_containers: dict):
|
||||
# preserves original api_pods iteration order
|
||||
for p in api_pods:
|
||||
name = p.get("Name")
|
||||
status = p.get("Status", "unknown")
|
||||
unit = map_pod_to_unit(name) if name else ""
|
||||
dashboard.append({
|
||||
"Name": name,
|
||||
"Status": status,
|
||||
"Containers": pod_to_containers.get(name, []),
|
||||
"Unit": unit,
|
||||
"Source": "podman",
|
||||
})
|
||||
|
||||
def _append_defined_pods_dashboard_rows(dashboard: list, by_name: dict, root_dir: str):
|
||||
# preserves original os.walk order and file iteration order
|
||||
for root, _, files in os.walk(root_dir):
|
||||
for f in files:
|
||||
if f.endswith((".yaml", ".yml")):
|
||||
base = os.path.splitext(os.path.basename(f))[0]
|
||||
pod_name = f"pod{base}"
|
||||
unit_name = map_pod_to_unit(pod_name)
|
||||
|
||||
if pod_name not in by_name:
|
||||
code, out = systemctl_func(["systemctl", "--user", "is-active", unit_name])
|
||||
status = (out or "").strip() or ("active" if code == 0 else "inactive")
|
||||
dashboard.append({
|
||||
"Name": pod_name,
|
||||
"Status": status,
|
||||
"Containers": [],
|
||||
"Unit": unit_name,
|
||||
"Source": "systemd",
|
||||
})
|
||||
|
||||
def try_systemd_pod_action(action: str, podname: str):
|
||||
# If systemd unit exists/allowed, prefer it.
|
||||
unit = map_pod_to_unit(podname)
|
||||
if not unit:
|
||||
return None
|
||||
code, out = systemctl_func(["systemctl", "--user", action, unit])
|
||||
return {
|
||||
"method": "systemd",
|
||||
"pod": podname,
|
||||
"unit": unit,
|
||||
"cmd": f"systemctl --user {action} {unit}",
|
||||
"exit": code,
|
||||
"output": out,
|
||||
}
|
||||
|
||||
@router.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 podman_get_json(url)
|
||||
|
||||
@router.post("/actions/{action}/{name}")
|
||||
def take_action(action: str, name: str):
|
||||
# Legacy endpoint (keep behavior)
|
||||
possible_names = [name, f"pod{name}", f"pod-{name}"]
|
||||
|
||||
if action == "start":
|
||||
# STAP 1: Probeer direct de pod te starten (de 'Cockpit' methode)
|
||||
for target in possible_names:
|
||||
res = podman_post(f"{podman_api_base}/libpod/pods/{target}/start")
|
||||
if res.status_code in (200, 204):
|
||||
return {"status": "started", "target": target, "method": "direct"}
|
||||
|
||||
# STAP 2: Als direct starten faalt, probeer dan YAML opnieuw te deployen
|
||||
target_path = None
|
||||
for ext in (".yaml", ".yml"):
|
||||
cand = os.path.join(workloads_dir, f"{name}{ext}")
|
||||
if os.path.exists(cand):
|
||||
target_path = cand
|
||||
break
|
||||
|
||||
if target_path:
|
||||
with open(target_path, 'r') as file:
|
||||
yaml_content = file.read()
|
||||
res = podman_post(f"{podman_api_base}/libpod/kube/play", data=yaml_content)
|
||||
|
||||
# SPECIALE CASE: Pod bestaat al, forceer dan restart
|
||||
if res.status_code == 500 and "already exists" in res.text:
|
||||
print(f"DEBUG: Forceer herstart voor {name} wegens conflict")
|
||||
for target in possible_names:
|
||||
podman_delete(f"{podman_api_base}/libpod/pods/{target}?force=true")
|
||||
# Probeer het nu opnieuw
|
||||
retry_res = podman_post(f"{podman_api_base}/libpod/kube/play", data=yaml_content)
|
||||
return retry_res.json()
|
||||
|
||||
return res.json()
|
||||
|
||||
return {"status": "unknown", "method": "no_yaml_found"}
|
||||
|
||||
if action == "stop":
|
||||
for target in possible_names:
|
||||
res = podman_post(f"{podman_api_base}/libpod/pods/{target}/stop")
|
||||
if res.status_code in (200, 204):
|
||||
return {"status": "stopped", "target": target}
|
||||
return {"status": "not found"}
|
||||
|
||||
return {"status": "unknown"}
|
||||
|
||||
@router.get("/pods-dashboard")
|
||||
def pods_dashboard():
|
||||
dashboard = []
|
||||
|
||||
# 0) Bouw mapping: pod_name -> [container_names...]
|
||||
containers = podman_get_json(f"{podman_api_base}/libpod/containers/json?all=true")
|
||||
pod_to_containers = build_pod_to_containers_map(containers)
|
||||
|
||||
# 1) A) echte pods
|
||||
api_pods = podman_get_json(f"{podman_api_base}/libpod/pods/json?all=true")
|
||||
by_name = {p.get("Name"): p for p in api_pods}
|
||||
|
||||
_append_podman_pods_dashboard_rows(dashboard, api_pods, pod_to_containers)
|
||||
|
||||
# 1) B) defined pods via workloads scan
|
||||
# Based on YAML files in WORKLOADS_DIR; show even if not running.
|
||||
_append_defined_pods_dashboard_rows(dashboard, by_name, workloads_dir)
|
||||
|
||||
return dashboard
|
||||
|
||||
@router.post("/pods/actions/{action}/{podname}")
|
||||
def pod_action_prefer_systemd(action: str, podname: str):
|
||||
if action not in ("start", "stop", "restart"):
|
||||
return {"error": "Invalid action"}, 400
|
||||
|
||||
def _systemd_call():
|
||||
return try_systemd_pod_action(action, podname)
|
||||
|
||||
def _podman_call(systemd_res):
|
||||
if systemd_res:
|
||||
note = "systemd failed; falling back to podman"
|
||||
podman = podman_action_post("pods", podname, action).json()
|
||||
return {"method": "systemd_then_podman", "note": note, "systemd": systemd_res, "podman": podman}
|
||||
return {"method": "podman", "result": podman_action_post("pods", podname, action).json()}
|
||||
|
||||
return systemd_then_podman(_systemd_call, _podman_call)
|
||||
|
||||
return router
|
||||
Reference in New Issue
Block a user