package infra import ( "encoding/json" "fmt" "os/exec" ) // DockerListContainers lista contenedores Docker. Si all es true, incluye los detenidos. func DockerListContainers(all bool) ([]ContainerInfo, error) { args := []string{"ps", "--format", "{{json .}}", "--no-trunc"} if all { args = append(args, "-a") } out, err := exec.Command("docker", args...).Output() if err != nil { return nil, fmt.Errorf("docker ps: %w", err) } if len(out) == 0 { return nil, nil } var containers []ContainerInfo for _, line := range splitLines(out) { if len(line) == 0 { continue } var raw struct { ID string `json:"ID"` Names string `json:"Names"` Image string `json:"Image"` Status string `json:"Status"` State string `json:"State"` Ports string `json:"Ports"` Created string `json:"CreatedAt"` Labels string `json:"Labels"` } if err := json.Unmarshal(line, &raw); err != nil { continue } containers = append(containers, ContainerInfo{ ID: raw.ID[:12], Name: raw.Names, Image: raw.Image, Status: raw.Status, State: raw.State, Ports: raw.Ports, Created: raw.Created, Labels: parseLabels(raw.Labels), }) } return containers, nil } // splitLines divide bytes en lĂ­neas individuales. func splitLines(data []byte) [][]byte { var lines [][]byte start := 0 for i, b := range data { if b == '\n' { lines = append(lines, data[start:i]) start = i + 1 } } if start < len(data) { lines = append(lines, data[start:]) } return lines } // parseLabels convierte "key=val,key2=val2" en map. func parseLabels(s string) map[string]string { m := make(map[string]string) if s == "" { return m } for _, part := range splitByComma(s) { for i := 0; i < len(part); i++ { if part[i] == '=' { m[part[:i]] = part[i+1:] break } } } return m } func splitByComma(s string) []string { var parts []string start := 0 for i := 0; i < len(s); i++ { if s[i] == ',' { parts = append(parts, s[start:i]) start = i + 1 } } if start < len(s) { parts = append(parts, s[start:]) } return parts }