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) _, err := cmd.CombinedOutput() if err != nil { return err } // 推送到仓库 script = "docker " + "-H " + dockerHost + " push " + imageRef cmd = exec.Command("sh", "-c", script) _, err = cmd.CombinedOutput() if err != nil { return err } 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, 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("容器未开放端口" + 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 }