refactor(api): introduce shared common helpers (mechanical extract)

This commit is contained in:
kodi
2026-02-28 09:14:35 +01:00
parent a8d62fa340
commit 61b2748854
3 changed files with 92 additions and 34 deletions
+1
View File
@@ -8,4 +8,5 @@ COPY app_files.py .
COPY app_networks.py .
COPY app_pods.py .
COPY app_containers.py .
COPY common.py .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
+24 -34
View File
@@ -7,6 +7,18 @@ from app_containers import init_containers_router, start_stats_poller
from app_networks import init_networks_router
from fastapi import FastAPI, HTTPException
import requests_unixsocket
from common import (
_build_pod_to_containers_map as _common_build_pod_to_containers_map,
_map_pod_to_unit as _common_map_pod_to_unit,
_podman_action_post as _common_podman_action_post,
_podman_delete as _common_podman_delete,
_podman_get_json as _common_podman_get_json,
_podman_get_json_checked as _common_podman_get_json_checked,
_podman_get_text as _common_podman_get_text,
_podman_post as _common_podman_post,
_systemctl as _common_systemctl,
_systemd_then_podman as _common_systemd_then_podman,
)
import uvicorn
app = FastAPI(title="Podman MVP Control Plane", root_path="/api")
@@ -22,28 +34,26 @@ async def _startup_stats_poller():
# Centralize Podman socket and systemctl invocation.
# MUST NOT change endpoint outputs, status codes, or side-effects.
def _podman_get_json_checked(url: str):
return _common_podman_get_json_checked(SESSION, url)
def _podman_get_json(url: str):
return SESSION.get(url).json()
return _common_podman_get_json(SESSION, url)
def _podman_get_text(url: str) -> str:
return SESSION.get(url).text
return _common_podman_get_text(SESSION, url)
def _podman_post(url: str, **kwargs):
return SESSION.post(url, **kwargs)
return _common_podman_post(SESSION, url, **kwargs)
def _podman_action_post(kind: str, name: str, action: str):
if kind == "pods":
url = f"{PODMAN_API_BASE}/libpod/pods/{name}/{action}"
else:
url = f"{PODMAN_API_BASE}/libpod/containers/{name}/{action}"
return _podman_post(url)
return _common_podman_action_post(SESSION, PODMAN_API_BASE, kind, name, action)
def _podman_delete(url: str):
return SESSION.delete(url)
return _common_podman_delete(SESSION, url)
def _systemctl(cmd):
# Proxy to existing run() to avoid behavioral changes.
return run(cmd)
return _common_systemctl(cmd, run)
def _run_systemctl_action(action: str, unit: str):
cmd = ["systemctl", "--user", action, unit]
@@ -83,35 +93,15 @@ def health():
# --- DASHBOARD HELPERS (contract-neutral, no ordering/sorting changes) ---
def _build_pod_to_containers_map(containers: list):
# preserves original order of containers processing; no sorting added
pod_to_containers = {}
for c in containers:
pod_name = c.get("PodName") or ""
if pod_name:
pod_to_containers.setdefault(pod_name, []).append((c.get("Names") or ["?"])[0])
return pod_to_containers
return _common_build_pod_to_containers_map(containers)
def _map_pod_to_unit(podname: str) -> str | None:
"""
HOTFIX 3.1 FIX 1:
If podname starts with "pod", map to <rest>.service (e.g. podmediaserver -> mediaserver.service)
Else: <podname>.service
"""
if not podname:
return None
if podname.startswith("pod"):
return f"{podname[3:]}.service"
return f"{podname}.service"
return _common_map_pod_to_unit(podname)
def _systemd_then_podman(systemd_callable, podman_callable):
systemd_res = systemd_callable()
if systemd_res is not None:
if isinstance(systemd_res, dict) and systemd_res.get("exit", 1) == 0:
return systemd_res
return podman_callable(systemd_res)
return podman_callable(None)
return _common_systemd_then_podman(systemd_callable, podman_callable)
# --- ROUTERS ---
+67
View File
@@ -0,0 +1,67 @@
from fastapi import HTTPException
def _podman_get_json_checked(session, url: str):
r = session.get(url)
if r.status_code >= 400:
raise HTTPException(status_code=502, detail=r.text)
try:
return r.json()
except Exception:
raise HTTPException(status_code=502, detail=r.text)
def _podman_get_json(session, url: str):
return session.get(url).json()
def _podman_get_text(session, url: str) -> str:
return session.get(url).text
def _podman_post(session, url: str, **kwargs):
return session.post(url, **kwargs)
def _podman_action_post(session, podman_api_base: str, kind: str, name: str, action: str):
if kind == "pods":
url = f"{podman_api_base}/libpod/pods/{name}/{action}"
else:
url = f"{podman_api_base}/libpod/containers/{name}/{action}"
return _podman_post(session, url)
def _podman_delete(session, url: str):
return session.delete(url)
def _systemctl(cmd, run_func):
# Proxy to existing run() to avoid behavioral changes.
return run_func(cmd)
def _build_pod_to_containers_map(containers: list):
# preserves original order of containers processing; no sorting added
pod_to_containers = {}
for c in containers:
pod_name = c.get("PodName") or ""
if pod_name:
pod_to_containers.setdefault(pod_name, []).append((c.get("Names") or ["?"])[0])
return pod_to_containers
def _map_pod_to_unit(podname: str) -> str | None:
if not podname:
return None
if podname.startswith("pod"):
return f"{podname[3:]}.service"
return f"{podname}.service"
def _systemd_then_podman(systemd_callable, podman_callable):
systemd_res = systemd_callable()
if systemd_res is not None:
if isinstance(systemd_res, dict) and systemd_res.get("exit", 1) == 0:
return systemd_res
return podman_callable(systemd_res)
return podman_callable(None)