* [Chore] format code style * [Chore] go mod tidy * remove deprecated config * Update services/devstar_devcontainer/docker_agent/AssignDevcontainerCr…
199 lines
6.0 KiB
Go
199 lines
6.0 KiB
Go
package docker_agent
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"io/ioutil"
|
||
"net/url"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
|
||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||
"code.gitea.io/gitea/modules/log"
|
||
"code.gitea.io/gitea/modules/setting"
|
||
devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto"
|
||
"github.com/docker/docker/api/types"
|
||
"github.com/docker/docker/api/types/container"
|
||
"github.com/docker/docker/client"
|
||
"github.com/docker/go-connections/nat"
|
||
apimachinery_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
)
|
||
|
||
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",
|
||
}
|
||
|
||
func AssignDevcontainerCreationDockerOperator(ctx *context.Context, newDevContainer *devcontainer_service_dto.CreateDevcontainerDTO) error {
|
||
log.Info("Docker create container.....")
|
||
// 1. 创建docker client
|
||
cli, err := CreateDockerClient(ctx)
|
||
defer cli.Close()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 2. 创建容器
|
||
opts := &devcontainer_dto.CreateDevcontainerOptions{
|
||
Name: newDevContainer.Name,
|
||
CreateOptions: apimachinery_meta_v1.CreateOptions{},
|
||
Image: newDevContainer.Image,
|
||
CommandList: []string{
|
||
"sh",
|
||
"-c",
|
||
"rm -f /etc/ssh/ssh_host_*; ssh-keygen -A ; service ssh start ; while true; do sleep 60; done",
|
||
},
|
||
SSHPublicKeyList: newDevContainer.SSHPublicKeyList,
|
||
GitRepositoryURL: newDevContainer.GitRepositoryURL,
|
||
}
|
||
|
||
// 2. 创建成功,取回集群中的 DevContainer
|
||
port, err := CreateDevcontainer(ctx, cli, opts)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// 3. 将分配的 NodePort Service 写回 newDevcontainer,供写入数据库进行下一步操作
|
||
uint16Value, _ := strconv.ParseUint(port, 10, 16)
|
||
newDevContainer.DevcontainerPort = uint16(uint16Value)
|
||
return nil
|
||
}
|
||
|
||
func CreateDevcontainer(ctx *context.Context, cli *client.Client, opts *devcontainer_dto.CreateDevcontainerOptions) (string, error) {
|
||
if ctx == nil || opts == nil {
|
||
return "", fmt.Errorf("拉取镜像失败:环境为空")
|
||
}
|
||
// 拉取镜像
|
||
err := pullImage(cli, opts.Image)
|
||
if err != nil {
|
||
return "", fmt.Errorf("拉取镜像失败:%v", err)
|
||
}
|
||
// 创建并启动容器
|
||
port, err := createAndStartContainer(cli, opts)
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建或启动容器失败:%v", err)
|
||
}
|
||
return port, nil
|
||
}
|
||
|
||
// pullImage 用于拉取指定的 Docker 镜像
|
||
func pullImage(cli *client.Client, image string) error {
|
||
ctx := context.Background()
|
||
resp, err := cli.ImagePull(ctx, image, types.ImagePullOptions{})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Close()
|
||
|
||
_, err = io.Copy(os.Stdout, resp)
|
||
return err
|
||
}
|
||
|
||
func createAndStartContainer(cli *client.Client, opts *devcontainer_dto.CreateDevcontainerOptions) (string, error) {
|
||
ctx := context.Background()
|
||
|
||
// 创建容器配置
|
||
config := &container.Config{
|
||
Image: opts.Image,
|
||
Cmd: opts.CommandList,
|
||
AttachStdout: true,
|
||
AttachStderr: true,
|
||
Tty: true,
|
||
OpenStdin: true,
|
||
ExposedPorts: nat.PortSet{
|
||
"22/tcp": struct{}{},
|
||
},
|
||
}
|
||
|
||
// 设置容器主机配置
|
||
hostConfig := &container.HostConfig{
|
||
// PortBindings: nat.PortMap{
|
||
// "22/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: strconv.Itoa(int(opts.ServicePort))}}, // 将容器的22端口映射到宿主机的8080端口
|
||
// },
|
||
ExtraHosts: []string{"host.docker.internal:" + setting.Devstar.Devcontainer.Host},
|
||
PublishAllPorts: true,
|
||
}
|
||
|
||
// 创建容器
|
||
resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, opts.Name)
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建或启动容器失败:%v", err)
|
||
}
|
||
|
||
// 启动容器
|
||
err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建或启动容器失败:%v", err)
|
||
}
|
||
// 将公钥数组转换为字符串
|
||
keysString := strings.Join(opts.SSHPublicKeyList, "\n")
|
||
log.Info(fmt.Sprintf("keysString %s", keysString))
|
||
// 解析原始 URL
|
||
parsedURL, err := url.Parse(opts.GitRepositoryURL)
|
||
if err != nil {
|
||
fmt.Printf("Error parsing URL: %v\n", err)
|
||
return "", fmt.Errorf("error pull code from git: %v", err)
|
||
}
|
||
// 获取主机和端口
|
||
hostParts := strings.Split(parsedURL.Host, ":")
|
||
|
||
port := ""
|
||
if len(hostParts) > 1 {
|
||
port = hostParts[1]
|
||
}
|
||
// 修改主机部分
|
||
newHost := "host.docker.internal"
|
||
if port != "" {
|
||
newHost += ":" + port
|
||
}
|
||
|
||
// 设置新的主机
|
||
parsedURL.Host = newHost
|
||
|
||
// 生成新的 URL
|
||
newURL := parsedURL.String()
|
||
cmd := []string{
|
||
"sh", "-c",
|
||
"if [ ! -d '/data/workspace' ]; then git clone " + newURL + " /data/workspace && echo \"Git Repository cloned.\"; else echo \"Folder already exists.\"; fi; mkdir -p ~/test; mkdir -p ~/.ssh ; chmod 700 ~/.ssh; echo \"" + keysString + "\" > ~/.ssh/authorized_keys ; chmod 600 ~/.ssh/authorized_keys; ",
|
||
}
|
||
// 创建 exec 实例
|
||
ex, err := cli.ContainerExecCreate(context.Background(), resp.ID, types.ExecConfig{
|
||
Cmd: cmd,
|
||
AttachStdout: true,
|
||
AttachStderr: true,
|
||
})
|
||
if err != nil {
|
||
return "", fmt.Errorf("启动执行器失败:%v", err)
|
||
}
|
||
execResp, err := cli.ContainerExecAttach(context.Background(), ex.ID, types.ExecStartCheck{})
|
||
if err != nil {
|
||
return "", fmt.Errorf("执行命令失败:%v", err)
|
||
}
|
||
output, err := ioutil.ReadAll(execResp.Reader)
|
||
log.Info("Command output:\n%s\n", output)
|
||
// 获取容器详细信息
|
||
containerJSON, err := cli.ContainerInspect(context.Background(), resp.ID)
|
||
if err != nil {
|
||
return "", fmt.Errorf("获取容器信息失败:%v", err)
|
||
}
|
||
|
||
// 获取端口映射信息
|
||
portBindings := containerJSON.NetworkSettings.Ports
|
||
for containerPort, bindings := range portBindings {
|
||
for _, binding := range bindings {
|
||
log.Info("Container Port %s is mapped to Host Port %s on IP %s\n", containerPort, binding.HostPort, binding.HostIP)
|
||
if containerPort.Port() == "22" {
|
||
return binding.HostPort, nil
|
||
}
|
||
|
||
}
|
||
}
|
||
return "", fmt.Errorf("未找到映射的端口")
|
||
}
|