from typing import Any from fastapi import APIRouter, Depends, HTTPException, Query from app.auth import verify_token from app.ops_runner import run_command router = APIRouter() _DOCKER = "docker" def _container_name(project: str, env: str, service: str) -> str: """ Derive the Docker container name from project, env, and service. Docker Compose v2 default: {project}-{env}-{service}-1 """ return f"{project}-{env}-{service}-1" @router.get("/logs/{project}/{env}/{service}", summary="Get container logs") async def get_logs( project: str, env: str, service: str, lines: int = Query(default=100, ge=1, le=10000, description="Number of log lines to return"), _: str = Depends(verify_token), ) -> dict[str, Any]: """ Fetch the last N lines of logs from a container. Uses `docker logs --tail {lines} {container}`. """ container = _container_name(project, env, service) result = await run_command( [_DOCKER, "logs", "--tail", str(lines), container], timeout=30, ) # docker logs writes to stderr by default; treat combined output as logs combined = result["output"] + result["error"] if not result["success"] and not combined.strip(): raise HTTPException( status_code=500, detail=f"Failed to retrieve logs for container '{container}'", ) return { "container": container, "lines": lines, "logs": combined, } @router.post("/restart/{project}/{env}/{service}", summary="Restart a container") async def restart_service( project: str, env: str, service: str, _: str = Depends(verify_token), ) -> dict[str, Any]: """ Restart a Docker container via `docker restart {container}`. """ container = _container_name(project, env, service) result = await run_command( [_DOCKER, "restart", container], timeout=60, ) if not result["success"]: raise HTTPException( status_code=500, detail=f"Failed to restart container '{container}': {result['error'] or result['output']}", ) return { "success": True, "container": container, "message": f"Container '{container}' restarted successfully", }