239 lines
5.4 KiB
Go
239 lines
5.4 KiB
Go
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
|
||
}
|