"""Single-container inspect + lifecycle for compose-managed containers.""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, Request from sqlmodel import Session from auth import get_current_user, require_admin from database import get_session from docker_client import DockerError from models.user import User from services import audit_service, container_service router = APIRouter(prefix="/api/containers", tags=["containers"]) #: Docker errors that are the caller's to fix, not a daemon failure (the global #: handler turns every DockerError into a 502). _STATUS = {"not_found": 404, "not_managed": 404, "bad_action": 400} def _ip(request: Request) -> str: return request.client.host if request.client else "unknown" def _raise(exc: DockerError): status = _STATUS.get(exc.error) if status: raise HTTPException(status_code=status, detail=exc.detail or exc.error) from exc raise exc @router.get("/{container_id}") def inspect(container_id: str, user: User = Depends(get_current_user)) -> dict: try: data = container_service.inspect_container(container_id) except DockerError as exc: _raise(exc) if user.role != "admin": # The environment is where a container's credentials live — the same # values the stack's .env holds, which the read-only role does not get # either. Names stay visible, values do not. data["env"] = [entry.split("=", 1)[0] + "=••••••" if "=" in entry else entry for entry in data.get("env") or []] return data @router.post("/{container_id}/{action}") def action( container_id: str, action: str, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin), ) -> dict: try: result = container_service.container_action(container_id, action) except DockerError as exc: _raise(exc) audit_service.record( session, user=user.username, action=f"container.{action}", target=container_id[:12], ip=_ip(request), ) return result