Files
devstar/services/devstar_devcontainer/docker_agent/AssignDevcontainerCreationDockerOperator.go
xinitx 8d5b34e07e !16 [Feature] DevContainer 适配 Docker
* [Chore] format code style
* [Chore] go mod tidy
* remove deprecated config
* Update services/devstar_devcontainer/docker_agent/AssignDevcontainerCr…
2024-10-31 03:00:11 +00:00

199 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("未找到映射的端口")
}