264
internal/docker/client.go
Archivo normal
264
internal/docker/client.go
Archivo normal
@@ -0,0 +1,264 @@
|
||||
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/yourusername/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: v.PIDStats.Current,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Referencia en una nueva incidencia
Block a user