package docker import ( "context" "encoding/json" "fmt" "io" "net" "net/http" "time" ) // DockerClient communicates with the Docker daemon via Unix socket or TCP. type DockerClient struct { httpClient *http.Client baseURL string } // NewDockerClient creates a client that talks to /var/run/docker.sock. func NewDockerClient() *DockerClient { transport := &http.Transport{ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { return (&net.Dialer{Timeout: 5 * time.Second}).DialContext(ctx, "unix", "/var/run/docker.sock") }, } return &DockerClient{ httpClient: &http.Client{Transport: transport, Timeout: 10 * time.Second}, baseURL: "http://localhost", // host is ignored for unix socket } } func (c *DockerClient) get(path string, out interface{}) error { resp, err := c.httpClient.Get(c.baseURL + path) if err != nil { return fmt.Errorf("docker GET %s: %w", path, err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 400 { return fmt.Errorf("docker GET %s: status %d: %s", path, resp.StatusCode, string(body)) } return json.Unmarshal(body, out) } // ---- Types ---------------------------------------------------------------- type SwarmNode struct { ID string `json:"ID"` Description NodeDescription `json:"Description"` Status NodeStatus `json:"Status"` ManagerStatus *ManagerStatus `json:"ManagerStatus,omitempty"` Spec NodeSpec `json:"Spec"` UpdatedAt time.Time `json:"UpdatedAt"` CreatedAt time.Time `json:"CreatedAt"` } type NodeDescription struct { Hostname string `json:"Hostname"` Platform Platform `json:"Platform"` Resources Resources `json:"Resources"` Engine Engine `json:"Engine"` } type Platform struct { Architecture string `json:"Architecture"` OS string `json:"OS"` } type Resources struct { NanoCPUs int64 `json:"NanoCPUs"` MemoryBytes int64 `json:"MemoryBytes"` } type Engine struct { EngineVersion string `json:"EngineVersion"` } type NodeStatus struct { State string `json:"State"` Addr string `json:"Addr"` Message string `json:"Message"` } type ManagerStatus struct { Addr string `json:"Addr"` Leader bool `json:"Leader"` Reachability string `json:"Reachability"` } type NodeSpec struct { Role string `json:"Role"` Availability string `json:"Availability"` Labels map[string]string `json:"Labels"` } type Container struct { ID string `json:"Id"` Names []string `json:"Names"` Image string `json:"Image"` State string `json:"State"` Status string `json:"Status"` Labels map[string]string `json:"Labels"` } type ContainerStats struct { CPUStats CPUStats `json:"cpu_stats"` PreCPUStats CPUStats `json:"precpu_stats"` MemoryStats MemoryStats `json:"memory_stats"` } type CPUStats struct { CPUUsage CPUUsage `json:"cpu_usage"` SystemCPUUsage int64 `json:"system_cpu_usage"` OnlineCPUs int `json:"online_cpus"` } type CPUUsage struct { TotalUsage int64 `json:"total_usage"` PercpuUsage []int64 `json:"percpu_usage"` } type MemoryStats struct { Usage int64 `json:"usage"` MaxUsage int64 `json:"max_usage"` Limit int64 `json:"limit"` Stats map[string]int64 `json:"stats"` } type DockerInfo struct { Swarm SwarmInfo `json:"Swarm"` } type SwarmInfo struct { NodeID string `json:"NodeID"` LocalNodeState string `json:"LocalNodeState"` ControlAvailable bool `json:"ControlAvailable"` Managers int `json:"Managers"` Nodes int `json:"Nodes"` } // ---- Methods --------------------------------------------------------------- // IsSwarmActive checks if Docker Swarm is initialized. func (c *DockerClient) IsSwarmActive() bool { var info DockerInfo if err := c.get("/v1.44/info", &info); err != nil { return false } return info.Swarm.LocalNodeState == "active" } // GetSwarmInfo returns basic swarm info. func (c *DockerClient) GetSwarmInfo() (*DockerInfo, error) { var info DockerInfo if err := c.get("/v1.44/info", &info); err != nil { return nil, err } return &info, nil } // ListNodes returns all Swarm nodes (requires manager node). func (c *DockerClient) ListNodes() ([]SwarmNode, error) { var nodes []SwarmNode if err := c.get("/v1.44/nodes", &nodes); err != nil { return nil, err } return nodes, nil } // ListContainers returns all running containers on this host. func (c *DockerClient) ListContainers() ([]Container, error) { var containers []Container if err := c.get("/v1.44/containers/json?all=false", &containers); err != nil { return nil, err } return containers, nil } // GetContainerStats returns one-shot stats for a container (no streaming). func (c *DockerClient) GetContainerStats(containerID string) (*ContainerStats, error) { var stats ContainerStats if err := c.get(fmt.Sprintf("/v1.44/containers/%s/stats?stream=false", containerID), &stats); err != nil { return nil, err } return &stats, nil } // CalcCPUPercent computes CPU usage % from two consecutive stats snapshots. func CalcCPUPercent(stats *ContainerStats) float64 { cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage) systemDelta := float64(stats.CPUStats.SystemCPUUsage) - float64(stats.PreCPUStats.SystemCPUUsage) numCPU := float64(stats.CPUStats.OnlineCPUs) if numCPU == 0 { numCPU = float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) } if systemDelta > 0 && cpuDelta > 0 { return (cpuDelta / systemDelta) * numCPU * 100.0 } return 0 }