224 lines
5.6 KiB
Go
224 lines
5.6 KiB
Go
// 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+, PRD §9).
|
|
type Client struct {
|
|
cli *client.Client
|
|
}
|
|
|
|
// New membaca konfigurasi dari env (DOCKER_HOST dll) atau default socket.
|
|
func New() (*Client, error) {
|
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("docker: create client: %w", err)
|
|
}
|
|
return &Client{cli: cli}, nil
|
|
}
|
|
|
|
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] == "<none>:<none>" {
|
|
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
|
|
}
|