mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-06 17:30:25 +08:00
287 lines
6.8 KiB
Go
287 lines
6.8 KiB
Go
|
|
package dkm
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"encoding/json"
|
|||
|
|
"io"
|
|||
|
|
"mayfly-go/internal/machine/mcm"
|
|||
|
|
"mayfly-go/pkg/logx"
|
|||
|
|
"mayfly-go/pkg/pool"
|
|||
|
|
|
|||
|
|
"github.com/docker/docker/api/types"
|
|||
|
|
"github.com/docker/docker/api/types/container"
|
|||
|
|
"github.com/docker/docker/api/types/filters"
|
|||
|
|
"github.com/docker/docker/api/types/image"
|
|||
|
|
"github.com/docker/docker/api/types/network"
|
|||
|
|
"github.com/docker/docker/client"
|
|||
|
|
"github.com/gorilla/websocket"
|
|||
|
|
"github.com/spf13/cast"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
poolGroup = pool.NewPoolGroup[*Client]()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
DefaultServer = "unix:///var/run/docker.sock"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type DockerServer struct {
|
|||
|
|
Host string
|
|||
|
|
|
|||
|
|
Client *client.Client
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Client struct {
|
|||
|
|
DockerClient *client.Client
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c *Client) Close() error {
|
|||
|
|
return c.DockerClient.Close()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c *Client) Ping() error {
|
|||
|
|
_, err := c.DockerClient.Ping(context.Background())
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetCli get docker cli
|
|||
|
|
func GetCli(host string) (*Client, error) {
|
|||
|
|
if host == "" {
|
|||
|
|
host = DefaultServer
|
|||
|
|
}
|
|||
|
|
pool, err := poolGroup.GetCachePool(host, func() (*Client, error) {
|
|||
|
|
return NewClient(&DockerServer{Host: host})
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return pool.Get(context.Background())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewClient new docker client
|
|||
|
|
func NewClient(server *DockerServer) (*Client, error) {
|
|||
|
|
if server.Host == "" {
|
|||
|
|
server.Host = DefaultServer
|
|||
|
|
}
|
|||
|
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithHost(server.Host), client.WithAPIVersionNegotiation())
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return &Client{
|
|||
|
|
DockerClient: cli,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ContainerList() ([]container.Summary, error) {
|
|||
|
|
var (
|
|||
|
|
options container.ListOptions
|
|||
|
|
)
|
|||
|
|
options.All = true
|
|||
|
|
containers, err := c.DockerClient.ContainerList(context.Background(), options)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return containers, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ContainerStats(containerID string) (container.StatsResponse, error) {
|
|||
|
|
var stats container.StatsResponse
|
|||
|
|
res, err := c.DockerClient.ContainerStats(context.Background(), containerID, false)
|
|||
|
|
if err != nil {
|
|||
|
|
return stats, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
defer res.Body.Close()
|
|||
|
|
body, err := io.ReadAll(res.Body)
|
|||
|
|
if err != nil {
|
|||
|
|
return stats, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err := json.Unmarshal(body, &stats); err != nil {
|
|||
|
|
return stats, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return stats, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ContainerRestart(containerID string) error {
|
|||
|
|
return c.DockerClient.ContainerRestart(context.Background(), containerID, container.StopOptions{})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ContainerStop(containerID string) error {
|
|||
|
|
return c.DockerClient.ContainerStop(context.Background(), containerID, container.StopOptions{})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ContainerAttach(containerID string, wsConn *websocket.Conn, rows, cols int) error {
|
|||
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|||
|
|
defer cancel()
|
|||
|
|
|
|||
|
|
// 创建exec配置(启用TTY)
|
|||
|
|
execID, err := c.DockerClient.ContainerExecCreate(ctx, containerID, container.ExecOptions{
|
|||
|
|
AttachStdin: true,
|
|||
|
|
AttachStdout: true,
|
|||
|
|
AttachStderr: true,
|
|||
|
|
Tty: true,
|
|||
|
|
Cmd: []string{"/bin/bash"}, // 或指定其他shell
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
hijackedResp, err := c.DockerClient.ContainerExecAttach(ctx, execID.ID, container.ExecAttachOptions{
|
|||
|
|
Tty: true,
|
|||
|
|
ConsoleSize: &[2]uint{cast.ToUint(rows), cast.ToUint(cols)},
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
defer hijackedResp.Close()
|
|||
|
|
|
|||
|
|
wsConn.WriteMessage(websocket.TextMessage, []byte("\033[2J\033[3J\033[1;1H")) // 清屏
|
|||
|
|
|
|||
|
|
// 转发容器输出到前端
|
|||
|
|
go func() {
|
|||
|
|
buf := make([]byte, 1024)
|
|||
|
|
for {
|
|||
|
|
select {
|
|||
|
|
case <-ctx.Done():
|
|||
|
|
return
|
|||
|
|
default:
|
|||
|
|
n, err := hijackedResp.Reader.Read(buf)
|
|||
|
|
if err != nil {
|
|||
|
|
if err != io.EOF {
|
|||
|
|
logx.ErrorTrace("Read container output error:", err)
|
|||
|
|
}
|
|||
|
|
// 容器退出时主动关闭WebSocket
|
|||
|
|
wsConn.WriteMessage(websocket.CloseMessage, []byte{})
|
|||
|
|
cancel()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
wsConn.WriteMessage(websocket.TextMessage, buf[:n])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
for {
|
|||
|
|
select {
|
|||
|
|
case <-ctx.Done():
|
|||
|
|
return nil
|
|||
|
|
default:
|
|||
|
|
_, input, err := wsConn.ReadMessage()
|
|||
|
|
if err != nil {
|
|||
|
|
if websocket.IsUnexpectedCloseError(err) {
|
|||
|
|
logx.Debug("WebSocket closed:", err)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 解析消息
|
|||
|
|
msgObj, err := mcm.ParseMsg(input)
|
|||
|
|
if err != nil {
|
|||
|
|
wsConn.WriteMessage(websocket.TextMessage, []byte("failed to parse the message content..."))
|
|||
|
|
logx.Error("terminal message parsing failed: ", err)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
switch msgObj.Type {
|
|||
|
|
case mcm.MsgTypeResize:
|
|||
|
|
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
|||
|
|
c.DockerClient.ContainerExecResize(ctx, execID.ID, container.ResizeOptions{
|
|||
|
|
Height: cast.ToUint(msgObj.Rows),
|
|||
|
|
Width: cast.ToUint(msgObj.Cols),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
case mcm.MsgTypeData:
|
|||
|
|
data := []byte(msgObj.Msg)
|
|||
|
|
hijackedResp.Conn.Write(data)
|
|||
|
|
case mcm.MsgTypePing:
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ContainerRemove(containerID string) error {
|
|||
|
|
return c.DockerClient.ContainerRemove(context.Background(), containerID, container.RemoveOptions{Force: true, RemoveVolumes: true})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ContainerInspect(containerID string) (types.ContainerJSON, error) {
|
|||
|
|
return c.DockerClient.ContainerInspect(context.Background(), containerID)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ImageRemove(imageID string) error {
|
|||
|
|
if _, err := c.DockerClient.ImageRemove(context.Background(), imageID, image.RemoveOptions{Force: true}); err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) ImageList() ([]image.Summary, error) {
|
|||
|
|
return c.DockerClient.ImageList(context.Background(), image.ListOptions{
|
|||
|
|
All: false,
|
|||
|
|
ContainerCount: true,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) PullImage(imageName string, force bool) error {
|
|||
|
|
if !force {
|
|||
|
|
exist, err := c.CheckImageExist(imageName)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
if exist {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if _, err := c.DockerClient.ImagePull(context.Background(), imageName, image.PullOptions{}); err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) GetImageIDByName(imageName string) (string, error) {
|
|||
|
|
filter := filters.NewArgs()
|
|||
|
|
filter.Add("reference", imageName)
|
|||
|
|
list, err := c.DockerClient.ImageList(context.Background(), image.ListOptions{
|
|||
|
|
Filters: filter,
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
if len(list) > 0 {
|
|||
|
|
return list[0].ID, nil
|
|||
|
|
}
|
|||
|
|
return "", nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) CheckImageExist(imageName string) (bool, error) {
|
|||
|
|
filter := filters.NewArgs()
|
|||
|
|
filter.Add("reference", imageName)
|
|||
|
|
list, err := c.DockerClient.ImageList(context.Background(), image.ListOptions{
|
|||
|
|
Filters: filter,
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return false, err
|
|||
|
|
}
|
|||
|
|
return len(list) > 0, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) CreateNetwork(name string) error {
|
|||
|
|
_, err := c.DockerClient.NetworkCreate(context.Background(), name, network.CreateOptions{
|
|||
|
|
Driver: "bridge",
|
|||
|
|
})
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c Client) NetworkExist(name string) bool {
|
|||
|
|
var options network.ListOptions
|
|||
|
|
options.Filters = filters.NewArgs(filters.Arg("name", name))
|
|||
|
|
networks, err := c.DockerClient.NetworkList(context.Background(), options)
|
|||
|
|
if err != nil {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
return len(networks) > 0
|
|||
|
|
}
|