From 61b274885498346a5a13eb759eec019f13443fa9 Mon Sep 17 00:00:00 2001 From: kodi Date: Sat, 28 Feb 2026 09:14:35 +0100 Subject: [PATCH] refactor(api): introduce shared common helpers (mechanical extract) --- control/Dockerfile | 1 + control/app.py | 58 +++++++++++++++++---------------------- control/common.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 34 deletions(-) create mode 100644 control/common.py diff --git a/control/Dockerfile b/control/Dockerfile index c881057..8fb1378 100644 --- a/control/Dockerfile +++ b/control/Dockerfile @@ -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"] diff --git a/control/app.py b/control/app.py index d8d3fcd..3bb097a 100644 --- a/control/app.py +++ b/control/app.py @@ -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 .service (e.g. podmediaserver -> mediaserver.service) - Else: .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 --- diff --git a/control/common.py b/control/common.py new file mode 100644 index 0000000..e5882dd --- /dev/null +++ b/control/common.py @@ -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)