"""Docker по SSH: команды docker с JSON-выводом. Не требует docker CLI на клиенте.""" from __future__ import annotations import json from typing import List, Optional from app.core.ssh import SSHClient async def _docker(c: SSHClient, args: str, timeout: int = 120) -> str: r = await c.sudo(f"docker {args}", timeout=timeout) return r.stdout async def available(c: SSHClient) -> bool: r = await c.sudo("docker version --format '{{.Server.Version}}'", timeout=30) return r.ok and bool(r.stdout.strip()) async def server_version(c: SSHClient) -> str: r = await c.sudo("docker version --format '{{.Server.Version}}'", timeout=30) return r.stdout.strip() # ---------- контейнеры ---------- async def list_containers(c: SSHClient) -> List[dict]: out = await _docker(c, "ps -a --format '{{json .}}'") return [json.loads(l) for l in out.splitlines() if l.strip()] async def container_action(c: SSHClient, cid: str, action: str) -> str: assert action in ("start", "stop", "restart", "kill", "rm") return await _docker(c, f"{action} {cid}") async def container_logs(c: SSHClient, cid: str, tail: int = 200) -> str: return await _docker(c, f"logs --tail {tail} {cid}") async def container_inspect(c: SSHClient, cid: str) -> dict: out = await _docker(c, f"inspect {cid}") data = json.loads(out) return data[0] if data else {} async def container_stats(c: SSHClient) -> List[dict]: out = await _docker( c, "stats --no-stream --format '{{json .}}'", timeout=60 ) return [json.loads(l) for l in out.splitlines() if l.strip()] # ---------- образы ---------- async def list_images(c: SSHClient) -> List[dict]: out = await _docker(c, "images --format '{{json .}}'") return [json.loads(l) for l in out.splitlines() if l.strip()] async def pull(c: SSHClient, image: str): return c.run_stream(f"docker pull {image}", use_sudo=True) async def remove_image(c: SSHClient, image: str) -> str: return await _docker(c, f"rmi {image}") # ---------- тома ---------- async def list_volumes(c: SSHClient) -> List[dict]: out = await _docker(c, "volume ls --format '{{json .}}'") return [json.loads(l) for l in out.splitlines() if l.strip()] async def remove_volume(c: SSHClient, name: str) -> str: return await _docker(c, f"volume rm {name}") # ---------- сети ---------- async def list_networks(c: SSHClient) -> List[dict]: out = await _docker(c, "network ls --format '{{json .}}'") return [json.loads(l) for l in out.splitlines() if l.strip()] # ---------- система ---------- async def system_df(c: SSHClient) -> str: return await _docker(c, "system df") async def system_prune(c: SSHClient) -> str: return await _docker(c, "system prune -f", timeout=600) # ---------- compose ---------- async def compose_projects(c: SSHClient) -> List[dict]: out = await _docker(c, "compose ls --format json") try: return json.loads(out) except json.JSONDecodeError: return [] async def compose_action(c: SSHClient, workdir: str, action: str, timeout: int = 600) -> str: assert action in ("up -d", "down", "restart", "pull", "ps", "logs") return await _docker(c, f"compose -f {workdir}/docker-compose.yml {action}", timeout)