package docker import ( "context" "encoding/json" "fmt" "io" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" "github.com/manalejandro/buque/internal/models" ) // Client wraps Docker client operations type Client struct { cli *client.Client } // NewClient creates a new Docker client func NewClient() (*Client, error) { cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return nil, fmt.Errorf("failed to create Docker client: %w", err) } return &Client{cli: cli}, nil } // Close closes the Docker client connection func (c *Client) Close() error { return c.cli.Close() } // ListContainers lists all containers with optional filters func (c *Client) ListContainers(ctx context.Context, all bool) ([]types.Container, error) { options := container.ListOptions{ All: all, } containers, err := c.cli.ContainerList(ctx, options) if err != nil { return nil, fmt.Errorf("failed to list containers: %w", err) } return containers, nil } // GetContainerStats retrieves statistics for a container func (c *Client) GetContainerStats(ctx context.Context, containerID string) (*models.ContainerStats, error) { stats, err := c.cli.ContainerStats(ctx, containerID, false) if err != nil { return nil, fmt.Errorf("failed to get container stats: %w", err) } defer stats.Body.Close() var v *types.StatsJSON if err := json.NewDecoder(stats.Body).Decode(&v); err != nil { return nil, fmt.Errorf("failed to decode stats: %w", err) } // Calculate CPU percentage cpuDelta := float64(v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage) systemDelta := float64(v.CPUStats.SystemUsage - v.PreCPUStats.SystemUsage) cpuPercent := 0.0 if systemDelta > 0.0 && cpuDelta > 0.0 { cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 } // Calculate memory percentage memUsage := v.MemoryStats.Usage memLimit := v.MemoryStats.Limit memPercent := 0.0 if memLimit > 0 { memPercent = (float64(memUsage) / float64(memLimit)) * 100.0 } // Network stats var networkRx, networkTx uint64 for _, network := range v.Networks { networkRx += network.RxBytes networkTx += network.TxBytes } // Block I/O stats var blockRead, blockWrite uint64 for _, bioEntry := range v.BlkioStats.IoServiceBytesRecursive { switch bioEntry.Op { case "Read": blockRead += bioEntry.Value case "Write": blockWrite += bioEntry.Value } } containerStats := &models.ContainerStats{ ID: containerID, Name: v.Name, CPUPercentage: cpuPercent, MemoryUsage: memUsage, MemoryLimit: memLimit, MemoryPercent: memPercent, NetworkRx: networkRx, NetworkTx: networkTx, BlockRead: blockRead, BlockWrite: blockWrite, PIDs: 0, // PIDStats not available in newer Docker API } return containerStats, nil } // InspectContainer returns detailed container information func (c *Client) InspectContainer(ctx context.Context, containerID string) (types.ContainerJSON, error) { container, err := c.cli.ContainerInspect(ctx, containerID) if err != nil { return types.ContainerJSON{}, fmt.Errorf("failed to inspect container: %w", err) } return container, nil } // PullImage pulls a Docker image func (c *Client) PullImage(ctx context.Context, imageName string) error { out, err := c.cli.ImagePull(ctx, imageName, types.ImagePullOptions{}) if err != nil { return fmt.Errorf("failed to pull image %s: %w", imageName, err) } defer out.Close() // Read output to completion _, err = io.Copy(io.Discard, out) return err } // ListImages lists Docker images func (c *Client) ListImages(ctx context.Context) ([]types.ImageSummary, error) { images, err := c.cli.ImageList(ctx, types.ImageListOptions{}) if err != nil { return nil, fmt.Errorf("failed to list images: %w", err) } return images, nil } // RemoveImage removes a Docker image func (c *Client) RemoveImage(ctx context.Context, imageID string, force bool) error { _, err := c.cli.ImageRemove(ctx, imageID, types.ImageRemoveOptions{ Force: force, }) if err != nil { return fmt.Errorf("failed to remove image %s: %w", imageID, err) } return nil } // PruneImages removes unused images func (c *Client) PruneImages(ctx context.Context) error { _, err := c.cli.ImagesPrune(ctx, filters.Args{}) if err != nil { return fmt.Errorf("failed to prune images: %w", err) } return nil } // CreateNetwork creates a Docker network func (c *Client) CreateNetwork(ctx context.Context, name string) error { _, err := c.cli.NetworkCreate(ctx, name, types.NetworkCreate{ Driver: "bridge", }) if err != nil { return fmt.Errorf("failed to create network %s: %w", name, err) } return nil } // ListNetworks lists Docker networks func (c *Client) ListNetworks(ctx context.Context) ([]types.NetworkResource, error) { networks, err := c.cli.NetworkList(ctx, types.NetworkListOptions{}) if err != nil { return nil, fmt.Errorf("failed to list networks: %w", err) } return networks, nil } // NetworkExists checks if a network exists func (c *Client) NetworkExists(ctx context.Context, name string) (bool, error) { networks, err := c.ListNetworks(ctx) if err != nil { return false, err } for _, network := range networks { if network.Name == name { return true, nil } } return false, nil } // GetContainersByLabel returns containers filtered by label func (c *Client) GetContainersByLabel(ctx context.Context, label, value string) ([]types.Container, error) { filterArgs := filters.NewArgs() filterArgs.Add("label", fmt.Sprintf("%s=%s", label, value)) options := container.ListOptions{ All: true, Filters: filterArgs, } containers, err := c.cli.ContainerList(ctx, options) if err != nil { return nil, fmt.Errorf("failed to list containers by label: %w", err) } return containers, nil } // StopContainer stops a container func (c *Client) StopContainer(ctx context.Context, containerID string, timeout time.Duration) error { timeoutSeconds := int(timeout.Seconds()) if err := c.cli.ContainerStop(ctx, containerID, container.StopOptions{Timeout: &timeoutSeconds}); err != nil { return fmt.Errorf("failed to stop container %s: %w", containerID, err) } return nil } // RestartContainer restarts a container func (c *Client) RestartContainer(ctx context.Context, containerID string, timeout time.Duration) error { timeoutSeconds := int(timeout.Seconds()) if err := c.cli.ContainerRestart(ctx, containerID, container.StopOptions{Timeout: &timeoutSeconds}); err != nil { return fmt.Errorf("failed to restart container %s: %w", containerID, err) } return nil } // Ping checks if Docker daemon is reachable func (c *Client) Ping(ctx context.Context) error { _, err := c.cli.Ping(ctx) if err != nil { return fmt.Errorf("failed to ping Docker daemon: %w", err) } return nil } // GetDockerVersion returns Docker version information func (c *Client) GetDockerVersion(ctx context.Context) (types.Version, error) { version, err := c.cli.ServerVersion(ctx) if err != nil { return types.Version{}, fmt.Errorf("failed to get Docker version: %w", err) } return version, nil }