from __future__ import annotations from typing import Any from core.tools.base import BaseTool, ToolContext from core.tools.registry import registry from core.events import bus from core.subprocess import run_command class DockerTool(BaseTool): name = "docker" description = "Docker container and image management" # ========================================================= # EXECUTE ROUTER # ========================================================= def execute(self, payload: dict[str, Any], ctx: ToolContext): action = str(payload.get("action", "")).strip() bus.log( "DOCKER", "docker_execute", "INFO", {"action": action} ) match action: case "ps": return self.ps(payload) case "run": return self.run_container(payload, ctx) case "stop": return self.stop_container(payload) case "start": return self.start_container(payload) case "restart": return self.restart_container(payload) case "logs": return self.logs(payload) case "images": return self.images(payload) case "build": return self.build_image(payload, ctx) case _: raise ValueError(f"Unknown docker action: {action}") # ========================================================= # CONTAINERS LIST # ========================================================= def ps(self, payload: dict[str, Any]): all_containers = payload.get("all", False) cmd = ["docker", "ps"] if all_containers: cmd.append("-a") result = run_command(cmd=cmd) return { "action": "ps", "status": "ok" if result.get("return_code") == 0 else "error", "output": result.get("stdout", ""), "error": result.get("stderr", "") } # ========================================================= # RUN CONTAINER # ========================================================= def run_container(self, payload: dict[str, Any], ctx: ToolContext): image = payload.get("image") name = payload.get("name") args = payload.get("args", []) if not isinstance(image, str): raise ValueError("image must be string") if not isinstance(args, list): raise ValueError("args must be list") cmd = ["docker", "run"] if name: if not isinstance(name, str): raise ValueError("name must be string") cmd += ["--name", name] cmd += args cmd.append(image) if ctx.dry_run: return { "dry_run": True, "command": cmd } result = run_command(cmd=cmd) return { "action": "run", "image": image, "status": "ok" if result.get("return_code") == 0 else "error", "stdout": result.get("stdout", ""), "stderr": result.get("stderr", "") } # ========================================================= # STOP / START / RESTART # ========================================================= def stop_container(self, payload: dict[str, Any]): return self._simple_container_action("stop", payload) def start_container(self, payload: dict[str, Any]): return self._simple_container_action("start", payload) def restart_container(self, payload: dict[str, Any]): return self._simple_container_action("restart", payload) def _simple_container_action(self, action: str, payload: dict[str, Any]): container = payload.get("container") if not isinstance(container, str): raise ValueError("container must be string") result = run_command( cmd=["docker", action, container] ) return { "action": action, "container": container, "status": "ok" if result.get("return_code") == 0 else "error", "stdout": result.get("stdout", ""), "stderr": result.get("stderr", "") } # ========================================================= # LOGS # ========================================================= def logs(self, payload: dict[str, Any]): container = payload.get("container") tail = payload.get("tail", 100) if not isinstance(container, str): raise ValueError("container must be string") cmd = ["docker", "logs", "--tail", str(tail), container] result = run_command(cmd=cmd) return { "action": "logs", "container": container, "output": result.get("stdout", ""), "error": result.get("stderr", "") } # ========================================================= # IMAGES # ========================================================= def images(self, payload: dict[str, Any]): result = run_command(cmd=["docker", "images"]) return { "action": "images", "output": result.get("stdout", ""), "error": result.get("stderr", "") } # ========================================================= # BUILD IMAGE # ========================================================= def build_image(self, payload: dict[str, Any], ctx: ToolContext): path = payload.get("path", ".") tag = payload.get("tag") if not isinstance(path, str): raise ValueError("path must be string") cmd = ["docker", "build", "-t"] if isinstance(tag, str): cmd.append(tag) else: cmd.append("untagged-image") cmd.append(path) if ctx.dry_run: return { "dry_run": True, "command": cmd } result = run_command(cmd=cmd) return { "action": "build", "path": path, "status": "ok" if result.get("return_code") == 0 else "error", "stdout": result.get("stdout", ""), "stderr": result.get("stderr", "") } # ========================================================= # REGISTER TOOL # ========================================================= registry.register(DockerTool())