Matthias Nott
2026-02-21 15c6d258137464aad5bbcc53f6044ad628849048
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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",
    }