Files
DockerBot/internal/docker/docker.go
2026-04-13 21:42:04 +02:00

239 lines
5.4 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package docker
import (
"bytes"
"context"
"fmt"
"io"
"strconv"
"strings"
"time"
"fergalla.com/dockerbot/internal/apperrors"
"github.com/containerd/errdefs"
"github.com/moby/moby/api/pkg/stdcopy"
"github.com/moby/moby/client"
)
type DockerInfo struct {
ctxDocker context.Context
dockerClient *client.Client
}
/* type ContainerStats struct {
CPUPercent float64
MemUsage uint64
MemLimit uint64
MemPercent float64
NetInput uint64
NetOutput uint64
}
type statsResponse struct {
CPUStats struct {
CPUUsage struct {
TotalUsage uint64 `json:"total_usage"`
PercpuUsage []uint64 `json:"percpu_usage"`
} `json:"cpu_usage"`
SystemUsage uint64 `json:"system_cpu_usage"`
} `json:"cpu_stats"`
PreCPUStats struct {
CPUUsage struct {
TotalUsage uint64 `json:"total_usage"`
} `json:"cpu_usage"`
SystemUsage uint64 `json:"system_cpu_usage"`
} `json:"precpu_stats"`
MemoryStats struct {
Usage uint64 `json:"usage"`
Limit uint64 `json:"limit"`
} `json:"memory_stats"`
Networks map[string]struct {
RxBytes uint64 `json:"rx_bytes"`
TxBytes uint64 `json:"tx_bytes"`
} `json:"networks"`
} */
type ContainerInspectInfo struct {
ID string
Name string
Image string
Created time.Time
Path string
Command string
State string
Health string
RestartCount int
StartedAt time.Time
FinishedAt time.Time
Ports map[string]string
Mounts []string
Networks []string
}
type ContainerBasicInfo struct {
Name string
Image string
State string
Health string
}
func New() (*DockerInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
apiClient, err := client.New(client.FromEnv)
if err != nil {
return nil, err
}
return &DockerInfo{
dockerClient: apiClient,
ctxDocker: ctx,
}, nil
}
func (d *DockerInfo) ListContainers() ([]ContainerBasicInfo, error) {
containers, err := d.dockerClient.ContainerList(d.ctxDocker, client.ContainerListOptions{All: true})
if err != nil {
return nil, err
}
var basicInfo []ContainerBasicInfo
for _, c := range containers.Items {
name := "unknown"
if len(c.Names) > 0 {
name = c.Names[0][1:]
}
image := c.Image
state := c.State
health := c.Health.Status
basicInfo = append(basicInfo, ContainerBasicInfo{
Name: name,
Image: image,
State: string(state),
Health: string(health),
})
}
return basicInfo, nil
}
func (d *DockerInfo) GetContainerInfo(containerID string) (*ContainerInspectInfo, error) {
containerJSON, err := d.dockerClient.ContainerInspect(d.ctxDocker, containerID, client.ContainerInspectOptions{})
if err != nil {
if errdefs.IsNotFound(err) {
return nil, apperrors.ErrContainerNotFound
}
return nil, err
}
c := containerJSON.Container
info := &ContainerInspectInfo{
Name: strings.TrimPrefix(c.Name, "/"),
Image: c.Config.Image,
ID: c.ID,
Path: c.Path,
Command: c.Path + " " + strings.Join(c.Args, " "),
State: string(c.State.Status),
Health: "none",
RestartCount: c.RestartCount,
Created: strToTime(c.Created),
StartedAt: strToTime(c.State.StartedAt),
FinishedAt: strToTime(c.State.FinishedAt),
}
if c.State.Health != nil {
info.Health = string(c.State.Health.Status)
}
info.Ports = make(map[string]string)
for port, bindings := range c.NetworkSettings.Ports {
if len(bindings) > 0 {
info.Ports[port.String()] = bindings[0].HostPort
}
}
for _, m := range c.Mounts {
info.Mounts = append(info.Mounts, m.Source+"->"+m.Destination)
}
for name := range c.NetworkSettings.Networks {
info.Networks = append(info.Networks, name)
}
return info, nil
}
func (d *DockerInfo) ContainerAction(containerID string, action string) error {
var err error
switch action {
case "start":
_, err = d.dockerClient.ContainerStart(d.ctxDocker, containerID, client.ContainerStartOptions{})
case "stop":
_, err = d.dockerClient.ContainerStop(d.ctxDocker, containerID, client.ContainerStopOptions{})
case "restart":
_, err = d.dockerClient.ContainerRestart(d.ctxDocker, containerID, client.ContainerRestartOptions{})
case "pause":
_, err = d.dockerClient.ContainerPause(d.ctxDocker, containerID, client.ContainerPauseOptions{})
case "unpause":
_, err = d.dockerClient.ContainerUnpause(d.ctxDocker, containerID, client.ContainerUnpauseOptions{})
default:
return fmt.Errorf("unknown docker action: %s", action)
}
if errdefs.IsNotFound(err) {
return apperrors.ErrContainerNotFound
} else {
return err
}
}
func (d *DockerInfo) GetContainerLogs(containerID string, tail int) (string, error) {
opts := client.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
}
if tail != 0 {
opts.Tail = strconv.Itoa(tail)
}
reader, err := d.dockerClient.ContainerLogs(d.ctxDocker, containerID, opts)
if err != nil {
if errdefs.IsNotFound(err) {
return "", apperrors.ErrContainerNotFound
} else {
return "", err
}
}
defer reader.Close()
var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer
_, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, reader)
if err != nil && err != io.EOF {
return "", err
}
out := "\n\n⚠ STDOUT:\n" + stdoutBuf.String()
errOut := stderrBuf.String()
if errOut != "" {
out += "\n\n⚠ STDERR:\n" + errOut
}
return out, nil
}
func strToTime(date string) time.Time {
t, err := time.Parse(time.RFC3339Nano, date)
if err != nil {
t = time.Time{}
}
return t
}