// Package docker — lapisan tipis di atas Docker SDK untuk core tia. // Bebas UI: dipakai bersama oleh TUI dan CLI (F-028). package docker import ( "context" "fmt" "io" "strconv" "strings" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" ) // Client — pembungkus Docker SDK dengan negosiasi versi API // (mendukung Docker Engine 20.10+ DAN Podman dengan API kompatibel-Docker). type Client struct { cli *client.Client } func (c *Client) Close() error { return c.cli.Close() } // Info — ringkasan daemon untuk dashboard, doctor, dan status bar. type Info struct { ServerVersion string APIVersion string Containers int Running int Images int MemoryTotal int64 CPUs int } func (c *Client) Info(ctx context.Context) (Info, error) { i, err := c.cli.Info(ctx) if err != nil { return Info{}, fmt.Errorf("docker: info: %w", err) } return Info{ ServerVersion: i.ServerVersion, APIVersion: c.cli.ClientVersion(), // hasil negosiasi API Containers: i.Containers, Running: i.ContainersRunning, Images: i.Images, MemoryTotal: i.MemTotal, CPUs: i.NCPU, }, nil } // Ping — cek daemon hidup (dipakai doctor). func (c *Client) Ping(ctx context.Context) error { if _, err := c.cli.Ping(ctx); err != nil { return fmt.Errorf("docker: ping: %w", err) } return nil } // Container — ringkasan kontainer untuk sidebar/dashboard. type Container struct { ID string ImageID string // id image lengkap (untuk menandai image yang dipakai) Name string Image string State string // running, exited, paused, restarting, created, dead Status string // "Up 2 hours (healthy)" } // ListContainers mengembalikan semua kontainer (termasuk yang berhenti). func (c *Client) ListContainers(ctx context.Context) ([]Container, error) { list, err := c.cli.ContainerList(ctx, container.ListOptions{All: true}) if err != nil { return nil, fmt.Errorf("docker: container list: %w", err) } out := make([]Container, 0, len(list)) for _, ct := range list { name := "" if len(ct.Names) > 0 { name = strings.TrimPrefix(ct.Names[0], "/") } id := ct.ID if len(id) > 12 { id = id[:12] } out = append(out, Container{ ID: id, ImageID: ct.ImageID, Name: name, Image: ct.Image, State: ct.State, Status: ct.Status, }) } return out, nil } // Image — ringkasan image untuk daftar. type Image struct { ID string // 12 char tanpa prefix sha256: Tags []string Size int64 Created int64 // unix } func (c *Client) ListImages(ctx context.Context) ([]Image, error) { list, err := c.cli.ImageList(ctx, image.ListOptions{}) if err != nil { return nil, fmt.Errorf("docker: image list: %w", err) } out := make([]Image, 0, len(list)) for _, im := range list { tags := im.RepoTags if len(tags) == 1 && tags[0] == ":" { tags = nil } out = append(out, Image{ ID: trimID(im.ID), Tags: tags, Size: im.Size, Created: im.Created, }) } return out, nil } // Volume — ringkasan volume. type Volume struct { Name string Driver string Mountpoint string } func (c *Client) ListVolumes(ctx context.Context) ([]Volume, error) { resp, err := c.cli.VolumeList(ctx, volume.ListOptions{}) if err != nil { return nil, fmt.Errorf("docker: volume list: %w", err) } out := make([]Volume, 0, len(resp.Volumes)) for _, v := range resp.Volumes { out = append(out, Volume{Name: v.Name, Driver: v.Driver, Mountpoint: v.Mountpoint}) } return out, nil } // Network — ringkasan jaringan (subnet pertama dari IPAM). type Network struct { ID string Name string Driver string Scope string Subnet string } func (c *Client) ListNetworks(ctx context.Context) ([]Network, error) { list, err := c.cli.NetworkList(ctx, network.ListOptions{}) if err != nil { return nil, fmt.Errorf("docker: network list: %w", err) } out := make([]Network, 0, len(list)) for _, n := range list { subnet := "" for _, cfg := range n.IPAM.Config { if cfg.Subnet != "" { subnet = cfg.Subnet break } } out = append(out, Network{ ID: trimID(n.ID), Name: n.Name, Driver: n.Driver, Scope: n.Scope, Subnet: subnet, }) } return out, nil } // trimID — buang prefix sha256: dan potong ke 12 karakter. func trimID(s string) string { s = strings.TrimPrefix(s, "sha256:") if len(s) > 12 { s = s[:12] } return s } // StreamLogs membuka stream log kontainer (follow). Pemanggil wajib Close. // Stream mentah: kontainer TTY tidak dibungkus header multiplex. func (c *Client) StreamLogs(ctx context.Context, id string, tail int, timestamps bool) (io.ReadCloser, error) { rc, err := c.cli.ContainerLogs(ctx, id, container.LogsOptions{ ShowStdout: true, ShowStderr: true, Follow: true, Tail: strconv.Itoa(tail), Timestamps: timestamps, }) if err != nil { return nil, fmt.Errorf("docker: stream log: %w", err) } return rc, nil } // ContainerTty — apakah kontainer punya TTY (menentukan perlu demux atau tidak). func (c *Client) ContainerTty(ctx context.Context, id string) (bool, error) { in, err := c.cli.ContainerInspect(ctx, id) if err != nil { return false, fmt.Errorf("docker: inspect: %w", err) } return in.Config != nil && in.Config.Tty, nil }