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 }