Files
devstar/modules/docker/docker_api.go

265 lines
7.2 KiB
Go
Raw Permalink Normal View History

package docker
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/go-connections/nat"
)
// CreateDockerClient 创建Docker客户端
func CreateDockerClient(ctx context.Context) (*client.Client, error) {
log.Info("检查 Docker 环境")
// 1. 检查 Docker 环境
dockerSocketPath, err := GetDockerSocketPath()
if err != nil {
return nil, err
}
log.Info("dockerSocketPath: %s", dockerSocketPath)
// 2. 创建docker client 并且检查是否运行
opts := []client.Opt{
client.FromEnv,
}
if dockerSocketPath != "" {
opts = append(opts, client.WithHost(dockerSocketPath))
}
cli, err := client.NewClientWithOpts(opts...)
if err != nil {
return nil, err
}
_, err = cli.Ping(ctx)
if err != nil {
return nil, fmt.Errorf("docker未运行, %w", err)
}
return cli, nil
}
// GetDockerSocketPath 获取Docker Socket路径
func GetDockerSocketPath() (string, error) {
// 检查环境变量
socket, found := os.LookupEnv("DOCKER_HOST")
if found {
return socket, nil
}
// 检查常见的Docker socket路径
socketPaths := []string{
"/var/run/docker.sock",
"/run/podman/podman.sock",
"$HOME/.colima/docker.sock",
"$XDG_RUNTIME_DIR/docker.sock",
"$XDG_RUNTIME_DIR/podman/podman.sock",
`\\.\pipe\docker_engine`,
"$HOME/.docker/run/docker.sock",
}
// 测试Docker默认路径
for _, p := range socketPaths {
if _, err := os.Lstat(os.ExpandEnv(p)); err == nil {
if strings.HasPrefix(p, `\\.\`) {
return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), nil
}
return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), nil
}
}
// 找不到
return "", fmt.Errorf("Docker未安装")
}
func GetContainerID(cli *client.Client, containerName string) (string, error) {
containerJSON, err := cli.ContainerInspect(context.Background(), containerName)
if err != nil {
return "", err
}
return containerJSON.ID, nil
}
func GetContainerStatus(cli *client.Client, containerID string) (string, error) {
containerInfo, err := cli.ContainerInspect(context.Background(), containerID)
if err != nil {
return "", err
}
state := containerInfo.State
return state.Status, nil
}
func PushImage(dockerHost string, username string, password string, registryUrl string, imageRef string) error {
script := "docker " + "-H " + dockerHost + " login -u " + username + " -p " + password + " " + registryUrl + " "
cmd := exec.Command("sh", "-c", script)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%s \n 镜像登录失败: %s", string(output), err.Error())
}
// 推送到仓库
script = "docker " + "-H " + dockerHost + " push " + imageRef
cmd = exec.Command("sh", "-c", script)
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%s \n 镜像推送失败: %s", string(output), err.Error())
}
return nil
}
// PullImage 拉取Docker镜像
func PullImage(ctx context.Context, cli *client.Client, dockerHost, imageName string) error {
reader, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{})
if err != nil {
return err
}
defer reader.Close()
// 读取并丢弃输出,确保拉取完成
_, err = io.Copy(io.Discard, reader)
return err
}
// CreateAndStartContainer 创建并启动容器
func CreateAndStartContainer(ctx context.Context, cli *client.Client, imageName string, cmd []string, env []string, binds []string, ports nat.PortSet, containerName string) error {
// 配置容器
config := &container.Config{
Image: imageName,
Env: env,
}
if ports != nil {
config.ExposedPorts = ports
}
if cmd != nil {
config.Cmd = cmd
}
hostConfig := &container.HostConfig{
Binds: binds,
RestartPolicy: container.RestartPolicy{
Name: "always", // 设置为 always
},
PublishAllPorts: true,
}
// 创建容器
resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName)
if err != nil {
return err
}
// 启动容器
return cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
}
// DeleteContainer 停止并删除指定名称的容器
func DeleteContainer(ctx context.Context, cli *client.Client, containerName string) error {
// 首先尝试停止容器
timeout := 10
err := cli.ContainerStop(ctx, containerName, container.StopOptions{
Timeout: &timeout,
})
if err != nil {
// 如果容器已经停止或不存在,继续执行删除操作
// 这里不返回错误,因为我们的目标是删除容器
}
// 删除容器
err = cli.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{
Force: true, // 强制删除,即使容器正在运行
})
if err != nil {
return err
}
return nil
}
func IsContainerNotFound(err error) bool {
if client.IsErrNotFound(err) {
return true
}
return false
}
// 获取容器端口映射到了主机哪个端口,参数: DockerClient、containerName、容器端口号
func GetMappedPort(ctx context.Context, containerName string, port string) (uint16, error) {
// 创建 Docker 客户端
cli, err := CreateDockerClient(ctx)
if err != nil {
return 0, err
}
// 获取容器 ID
containerID, err := GetContainerID(cli, containerName)
if err != nil {
return 0, err
}
// 获取容器详细信息
containerJSON, err := cli.ContainerInspect(context.Background(), containerID)
if err != nil {
return 0, err
}
// 获取端口映射信息
portBindings := containerJSON.NetworkSettings.Ports
for containerPort, bindings := range portBindings {
for _, binding := range bindings {
log.Info("容器端口 %s 映射到主机 %s 端口 %s \n", containerPort, binding.HostIP, binding.HostPort)
if containerPort.Port() == port {
port_64, err := strconv.ParseUint(binding.HostPort, 10, 16)
if err != nil {
return 0, err
}
return uint16(port_64), nil
}
}
}
return 0, fmt.Errorf("容器未开放端口 %s", port)
}
func ExecCommandInContainer(ctx context.Context, cli *client.Client, containerName string, command string) (string, error) {
containerID, err := GetContainerID(cli, containerName)
if err != nil {
log.Info("创建执行实例失败", err)
return "", err
}
cmdList := []string{"sh", "-c", command}
execConfig := types.ExecConfig{
Cmd: cmdList,
AttachStdout: true,
AttachStderr: true,
}
// 创建执行实例
exec, err := cli.ContainerExecCreate(context.Background(), containerID, execConfig)
if err != nil {
log.Info("创建执行实例失败", err)
return "", err
}
// 附加到执行实例
resp, err := cli.ContainerExecAttach(context.Background(), exec.ID, types.ExecStartCheck{})
if err != nil {
log.Info("命令附加到执行实例失败", err)
return "", err
}
defer resp.Close()
// 启动执行实例
err = cli.ContainerExecStart(context.Background(), exec.ID, types.ExecStartCheck{})
if err != nil {
log.Info("启动执行实例失败", err)
return "", err
}
// 自定义缓冲区
var outBuf, errBuf strings.Builder
// 读取输出
_, err = stdcopy.StdCopy(&outBuf, &errBuf, resp.Reader)
if err != nil {
log.Info("Error reading output for command %v: %v\n", command, err)
return "", err
}
return outBuf.String() + errBuf.String(), nil
}