Files
homelab-infra/apps/dashboard/backend/app/clients/docker_proxy_client.py
T

104 lines
3.6 KiB
Python

from __future__ import annotations
import logging
from app.clients.beszel_client import BeszelClient
from app.clients.base import BaseHTTPClient
from app.config import Settings
from app.models.sources import DockerContainerSummary, DockerSnapshot
logger = logging.getLogger(__name__)
class DockerProxyClient(BaseHTTPClient):
def __init__(self, settings: Settings) -> None:
super().__init__(settings, "docker-proxy", settings.docker_proxy_base_url)
self.beszel_client = BeszelClient(settings)
async def fetch_containers(self) -> DockerSnapshot:
snapshot = DockerSnapshot()
payload = await self._request_json("GET", "/containers/json", params={"all": "true"})
if not isinstance(payload, list):
logger.warning("docker proxy returned non-list payload: %s", payload)
fallback = await self.beszel_client.fetch_container_snapshot()
if fallback.source_status == "online":
logger.info("docker proxy fallback to beszel containers succeeded")
return fallback
logger.warning(
"docker integration unavailable: docker proxy unreachable and beszel container fallback returned no usable data"
)
return snapshot
logger.info("docker proxy raw payload count: %s", len(payload))
logger.info("docker proxy raw payload sample: %s", payload[:3])
containers: list[DockerContainerSummary] = []
running = 0
stopped = 0
unhealthy = 0
for item in payload:
if not isinstance(item, dict):
continue
state = self._normalize_state(item)
if state == "running":
running += 1
elif state == "unhealthy":
unhealthy += 1
else:
stopped += 1
containers.append(
DockerContainerSummary(
id=str(item.get("Id") or item.get("ID") or ""),
name=self._normalize_name(item.get("Names")),
state=state,
status_text=str(item.get("Status") or item.get("State") or "unknown"),
image=str(item.get("Image") or ""),
health=self._extract_health(item),
)
)
normalized = DockerSnapshot(
source_status="online",
running=running,
stopped=stopped,
unhealthy=unhealthy,
total=len(containers),
containers=containers,
)
logger.info("docker proxy normalized snapshot: %s", normalized.model_dump())
return normalized
@staticmethod
def _normalize_name(names: object) -> str:
if isinstance(names, list) and names:
return str(names[0]).lstrip("/")
return "unknown"
@classmethod
def _normalize_state(cls, item: dict) -> str:
status_text = str(item.get("Status") or "").lower()
state = str(item.get("State") or "").lower()
health = cls._extract_health(item)
if health == "unhealthy" or "unhealthy" in status_text:
return "unhealthy"
if state == "running":
return "running"
if state:
return "stopped"
return "unknown"
@staticmethod
def _extract_health(item: dict) -> str | None:
if isinstance(item.get("Health"), str):
return str(item["Health"]).lower()
if isinstance(item.get("State"), dict):
health = item["State"].get("Health")
if isinstance(health, dict):
return str(health.get("Status") or "").lower() or None
return None