package runtime import ( "encoding/json" "fmt" "os/exec" "strings" ) type Docker struct{} func (d *Docker) Name() string { return "docker" } type dockerContainer struct { Names string `json:"Names"` Image string `json:"Image"` Command string `json:"Command"` State string `json:"State"` } func (d *Docker) ListRunning() ([]Container, error) { bin, err := exec.LookPath("docker") if err != nil { return nil, nil // docker not installed — not an error } out, err := exec.Command(bin, "ps", "--format", "json").Output() if err != nil { return nil, fmt.Errorf("docker ps: %w", err) } // Docker outputs one JSON object per line (not a JSON array) var containers []Container for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if line == "" { continue } var c dockerContainer if err := json.Unmarshal([]byte(line), &c); err != nil { continue } name := strings.TrimPrefix(c.Names, "/") if name == "" { continue } containers = append(containers, Container{ Name: name, Image: c.Image, Command: c.Command, Runtime: "docker", }) } return containers, nil } func (d *Docker) Exists(name string) (bool, error) { bin, err := exec.LookPath("docker") if err != nil { return false, nil } out, err := exec.Command(bin, "ps", "-a", "--format", "{{.Names}}").Output() if err != nil { return false, fmt.Errorf("docker ps -a: %w", err) } for _, line := range strings.Split(string(out), "\n") { if strings.TrimSpace(line) == name { return true, nil } } return false, nil }