Files
devstar/modules/docker/docker_api.go
2025-02-19 20:06:33 +08:00

227 lines
6.5 KiB
Go

package docker
import (
"context"
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
)
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 并且检查是否运行
cli, err := checkIfDockerRunning(*ctx, dockerSocketPath)
if err != nil {
return nil, err
}
return cli, nil
}
var commonSocketPaths = []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",
}
// 优先级配置文件的DockerHost字段 > DOCKER_HOST环境变量 > docker普通默认路径
func GetDockerSocketPath() (string, error) {
// 校验DockerHost配置
if setting.Devcontainer.DockerHost != "" && net.ParseIP(setting.Devcontainer.DockerHost) != nil {
return setting.Devcontainer.DockerHost, nil
}
// 检查环境变量
socket, found := os.LookupEnv("DOCKER_HOST")
if found {
return socket, nil
}
// 测试Docker默认路径
for _, p := range commonSocketPaths {
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 checkIfDockerRunning(ctx context.Context, configDockerHost string) (*client.Client, error) {
opts := []client.Opt{
client.FromEnv,
}
if configDockerHost != "" {
opts = append(opts, client.WithHost(configDockerHost))
}
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
}
// 获取容器端口映射到了主机哪个端口,参数: DockerClient、containerID、容器端口号
func GetMappedPort(cli *client.Client, containerID string, port string) (string, error) {
// 获取容器详细信息
containerJSON, err := cli.ContainerInspect(context.Background(), containerID)
if err != nil {
return "", 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 {
return binding.HostPort, nil
}
}
}
return "", fmt.Errorf("容器未开放端口" + port)
}
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()
log.Info(string(output))
if err != nil {
return err
}
// 推送到仓库
script = "docker " + "-H " + dockerHost + " push " + imageRef
cmd = exec.Command("sh", "-c", script)
output, err = cmd.CombinedOutput()
log.Info(string(output))
if err != nil {
return err
}
return nil
}
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 ExecCommandInContainer(cli *client.Client, containerID string, command []string) error {
execConfig := types.ExecConfig{
Cmd: command,
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
}
output, err := ioutil.ReadAll(resp.Reader)
log.Info("执行命令输出:\n%s\n", output)
return nil
}
func DeleteContainer(cli *client.Client, containerID string) error {
// 删除容器
options := types.ContainerRemoveOptions{
Force: true, // 强制删除正在运行的容器
RemoveVolumes: true, // 删除数据卷
RemoveLinks: false, // 删除链接(已弃用)
}
if err := cli.ContainerRemove(context.Background(), containerID, options); err != nil {
log.Info("删除 %s 容器失败: %v", containerID, err)
return err
}
log.Info("容器 %s 已成功删除\n", containerID)
return nil
}
// pullImage 用于拉取指定的 Docker 镜像
func PullImage(cli *client.Client, dockerHost string, image string) error {
script := "docker " + "-H " + dockerHost + " pull " + image
cmd := exec.Command("sh", "-c", script)
output, err := cmd.CombinedOutput()
log.Info(string(output))
if err != nil {
return err
}
return nil
}
func CreateAndStartContainer(cli *client.Client, image string, command strslice.StrSlice, env []string, binds []string, exposedPorts nat.PortSet, containerName string) error {
ctx := context.Background()
// 创建容器配置
config := &container.Config{
Image: image,
Cmd: command,
Env: env,
AttachStdout: true,
AttachStderr: true,
Tty: true,
OpenStdin: true,
ExposedPorts: exposedPorts,
}
// 设置容器配置
hostConfig := &container.HostConfig{
//Hosts
//ExtraHosts: []string{"host.docker.internal:" + host},
PublishAllPorts: true,
Binds: binds,
RestartPolicy: container.RestartPolicy{
Name: "always",
},
}
// 创建容器
resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName)
if err != nil {
return err
}
// 启动容器
err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
if err != nil {
return err
}
return nil
}