2025-08-16 18:31:14 +08:00
|
|
|
|
package devcontainer
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"archive/tar"
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
2025-08-21 21:31:51 +08:00
|
|
|
|
"os/exec"
|
2025-08-16 18:31:14 +08:00
|
|
|
|
"regexp"
|
|
|
|
|
|
"strings"
|
2025-08-29 16:08:02 +08:00
|
|
|
|
"time"
|
2025-08-16 18:31:14 +08:00
|
|
|
|
|
|
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
|
|
|
|
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
|
|
|
|
|
|
"code.gitea.io/gitea/models/repo"
|
|
|
|
|
|
"code.gitea.io/gitea/models/user"
|
|
|
|
|
|
docker_module "code.gitea.io/gitea/modules/docker"
|
|
|
|
|
|
"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"
|
2025-09-18 20:14:43 +08:00
|
|
|
|
"github.com/docker/docker/client"
|
2025-08-16 18:31:14 +08:00
|
|
|
|
"github.com/docker/docker/errdefs"
|
2025-08-21 21:31:51 +08:00
|
|
|
|
"github.com/docker/go-connections/nat"
|
2025-08-16 18:31:14 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func GetDevContainerStatusFromDocker(ctx context.Context, containerName string) (string, error) {
|
|
|
|
|
|
// 创建docker client
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
containerStatus, err := docker_module.GetContainerStatus(cli, containerID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
return containerStatus, nil
|
|
|
|
|
|
}
|
2025-09-17 11:05:42 +08:00
|
|
|
|
func CreateDevContainerByDockerAPI(ctx context.Context, newDevcontainer *devcontainer_models.Devcontainer, imageName string, repo *repo.Repository, publicKeyList []string) error {
|
2025-08-16 18:31:14 +08:00
|
|
|
|
dbEngine := db.GetEngine(ctx)
|
|
|
|
|
|
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
dockerSocket, err := docker_module.GetDockerSocketPath()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 21:31:51 +08:00
|
|
|
|
_, err = dbEngine.Table("devcontainer").
|
|
|
|
|
|
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
|
|
|
|
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 1})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Info("err %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" {
|
|
|
|
|
|
script := "docker " + "-H " + dockerSocket + " pull " + configurationModel.Image
|
|
|
|
|
|
cmd := exec.Command("sh", "-c", script)
|
|
|
|
|
|
err = cmd.Start()
|
|
|
|
|
|
if err != nil {
|
2025-08-16 18:31:14 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-08-21 21:31:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_, err = dbEngine.Table("devcontainer").
|
|
|
|
|
|
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
|
|
|
|
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 2})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
docker_module.CreateAndStartContainer(ctx, cli, imageName,
|
|
|
|
|
|
[]string{
|
|
|
|
|
|
"sh",
|
|
|
|
|
|
"-c",
|
|
|
|
|
|
"tail -f /dev/null;",
|
|
|
|
|
|
},
|
|
|
|
|
|
nil,
|
|
|
|
|
|
nil,
|
|
|
|
|
|
nat.PortSet{
|
2025-09-18 19:25:17 +08:00
|
|
|
|
nat.Port("22/tcp"): {},
|
2025-08-21 21:31:51 +08:00
|
|
|
|
},
|
|
|
|
|
|
newDevcontainer.Name)
|
|
|
|
|
|
_, err = dbEngine.Table("devcontainer").
|
|
|
|
|
|
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
|
|
|
|
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 3})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-10-12 23:06:34 +08:00
|
|
|
|
|
2025-08-21 21:31:51 +08:00
|
|
|
|
output, err := docker_module.ExecCommandInContainer(ctx, cli, newDevcontainer.Name,
|
2025-09-18 19:25:17 +08:00
|
|
|
|
`echo "`+newDevcontainer.DevcontainerHost+` host.docker.internal" | tee -a /etc/hosts;apt update;apt install -y git ;git clone `+strings.TrimSuffix(setting.AppURL, "/")+repo.Link()+" "+newDevcontainer.DevcontainerWorkDir+"/"+repo.Name+`; apt install -y ssh;echo "PubkeyAuthentication yes `+"\n"+`PermitRootLogin yes `+"\n"+`" | tee -a /etc/ssh/sshd_config;rm -f /etc/ssh/ssh_host_*; ssh-keygen -A; service ssh restart;mkdir -p ~/.ssh;chmod 700 ~/.ssh;echo "`+strings.Join(publicKeyList, "\n")+`" > ~/.ssh/authorized_keys;chmod 600 ~/.ssh/authorized_keys;`,
|
2025-08-21 21:31:51 +08:00
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
log.Info(output)
|
|
|
|
|
|
|
|
|
|
|
|
_, err = dbEngine.Table("devcontainer").
|
|
|
|
|
|
Where("user_id = ? AND repo_id = ? ", newDevcontainer.UserId, newDevcontainer.RepoId).
|
|
|
|
|
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-17 11:05:42 +08:00
|
|
|
|
func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *devcontainer_models.Devcontainer, repo *repo.Repository, publicKeyList []string) (string, error) {
|
2025-08-21 21:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
dbEngine := db.GetEngine(ctx)
|
|
|
|
|
|
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-21 21:31:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-21 21:31:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
var imageName = configurationModel.Image
|
|
|
|
|
|
dockerSocket, err := docker_module.GetDockerSocketPath()
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-21 21:31:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
if configurationModel.Build != nil && configurationModel.Build.Dockerfile != "" {
|
2025-08-16 18:31:14 +08:00
|
|
|
|
dockerfileContent, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+configurationModel.Build.Dockerfile)
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 创建构建上下文(包含Dockerfile的tar包)
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
|
tw := tar.NewWriter(&buf)
|
|
|
|
|
|
defer tw.Close()
|
|
|
|
|
|
// 添加Dockerfile到tar包
|
|
|
|
|
|
dockerfile := "Dockerfile"
|
|
|
|
|
|
content := []byte(dockerfileContent)
|
|
|
|
|
|
header := &tar.Header{
|
|
|
|
|
|
Name: dockerfile,
|
|
|
|
|
|
Size: int64(len(content)),
|
|
|
|
|
|
Mode: 0644,
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := tw.WriteHeader(header); err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
if _, err := tw.Write(content); err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 执行镜像构建
|
2025-08-21 21:31:51 +08:00
|
|
|
|
imageName = fmt.Sprintf("%d", newDevcontainer.UserId) + "-" + fmt.Sprintf("%d", newDevcontainer.RepoId) + "-dockerfile"
|
2025-08-16 18:31:14 +08:00
|
|
|
|
buildOptions := types.ImageBuildOptions{
|
|
|
|
|
|
Tags: []string{imageName}, // 镜像标签
|
|
|
|
|
|
}
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
buildResponse, err := cli.ImageBuild(
|
|
|
|
|
|
context.Background(),
|
|
|
|
|
|
&buf,
|
|
|
|
|
|
buildOptions,
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
output, err := io.ReadAll(buildResponse.Body)
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return "", err
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
log.Info(string(output))
|
2025-08-21 21:31:51 +08:00
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拉取镜像的命令
|
|
|
|
|
|
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
|
|
|
|
|
Output: "",
|
|
|
|
|
|
ListId: 1,
|
|
|
|
|
|
Status: "waitting",
|
|
|
|
|
|
UserId: newDevcontainer.UserId,
|
|
|
|
|
|
RepoId: newDevcontainer.RepoId,
|
|
|
|
|
|
Command: "docker " + "-H " + dockerSocket + " pull " + imageName + "\n",
|
|
|
|
|
|
DevcontainerId: newDevcontainer.Id,
|
|
|
|
|
|
}); err != nil {
|
|
|
|
|
|
log.Info("Failed to insert record: %v", err)
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return imageName, err
|
2025-08-21 21:31:51 +08:00
|
|
|
|
}
|
2025-09-04 10:48:46 +08:00
|
|
|
|
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
|
|
|
|
|
if err != nil {
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return imageName, err
|
2025-09-04 10:48:46 +08:00
|
|
|
|
}
|
2025-10-16 23:41:01 +08:00
|
|
|
|
var startCommand string = `docker -H ` + dockerSocket + ` create --restart=always --name ` + newDevcontainer.Name
|
2025-09-13 17:00:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 将每个端口转换为 "-p <port>" 格式
|
|
|
|
|
|
var portFlags string = " -p 22 "
|
|
|
|
|
|
|
|
|
|
|
|
exposedPorts := configurationModel.ExtractContainerPorts()
|
|
|
|
|
|
for _, port := range exposedPorts {
|
|
|
|
|
|
portFlags = portFlags + fmt.Sprintf(" -p %d ", port)
|
|
|
|
|
|
}
|
|
|
|
|
|
startCommand += portFlags
|
2025-10-12 23:06:34 +08:00
|
|
|
|
|
2025-09-13 17:00:58 +08:00
|
|
|
|
var envFlags string = ` -e RepoLink="` + strings.TrimSuffix(cfg.Section("server").Key("ROOT_URL").Value(), `/`) + repo.Link() + `" ` +
|
2025-09-29 20:13:44 +08:00
|
|
|
|
` -e DevstarHost="` + newDevcontainer.DevcontainerHost + `"` +
|
2025-10-16 23:41:01 +08:00
|
|
|
|
` -e WorkSpace="` + newDevcontainer.DevcontainerWorkDir + `/` + repo.Name + `" ` +
|
|
|
|
|
|
` -e DEVCONTAINER_STATUS="start" `
|
2025-09-13 17:00:58 +08:00
|
|
|
|
// 遍历 ContainerEnv 映射中的每个环境变量
|
|
|
|
|
|
for name, value := range configurationModel.ContainerEnv {
|
|
|
|
|
|
// 将每个环境变量转换为 "-e name=value" 格式
|
|
|
|
|
|
envFlags = envFlags + fmt.Sprintf(" -e %s=\"%s\" ", name, value)
|
|
|
|
|
|
}
|
|
|
|
|
|
startCommand += envFlags
|
|
|
|
|
|
if configurationModel.Init {
|
|
|
|
|
|
startCommand += " --init "
|
|
|
|
|
|
}
|
|
|
|
|
|
if configurationModel.Privileged {
|
|
|
|
|
|
startCommand += " --privileged "
|
|
|
|
|
|
}
|
|
|
|
|
|
var capAddFlags string
|
|
|
|
|
|
// 遍历 CapAdd 列表中的每个能力
|
|
|
|
|
|
for _, capability := range configurationModel.CapAdd {
|
|
|
|
|
|
// 将每个能力转换为 --cap-add=capability 格式
|
|
|
|
|
|
capAddFlags = capAddFlags + fmt.Sprintf(" --cap-add %s ", capability)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-18 15:03:56 +08:00
|
|
|
|
startCommand += capAddFlags
|
2025-09-13 17:00:58 +08:00
|
|
|
|
var securityOptFlags string
|
|
|
|
|
|
// 遍历 SecurityOpt 列表中的每个安全选项
|
|
|
|
|
|
for _, option := range configurationModel.SecurityOpt {
|
|
|
|
|
|
// 将每个选项转换为 --security-opt=option 格式
|
|
|
|
|
|
securityOptFlags = securityOptFlags + fmt.Sprintf(" --security-opt %s ", option)
|
|
|
|
|
|
}
|
|
|
|
|
|
startCommand += securityOptFlags
|
|
|
|
|
|
startCommand += " " + strings.Join(configurationModel.ExtractMountFlags(), " ") + " "
|
|
|
|
|
|
if configurationModel.WorkspaceFolder != "" {
|
|
|
|
|
|
startCommand += fmt.Sprintf(" -w %s ", configurationModel.WorkspaceFolder)
|
|
|
|
|
|
}
|
|
|
|
|
|
startCommand += " " + strings.Join(configurationModel.RunArgs, " ") + " "
|
2025-10-12 23:06:34 +08:00
|
|
|
|
overrideCommand := ""
|
|
|
|
|
|
if !configurationModel.OverrideCommand {
|
2025-10-16 23:41:01 +08:00
|
|
|
|
overrideCommand = ` sh -c "/home/webTerminal.sh" `
|
2025-10-12 23:06:34 +08:00
|
|
|
|
startCommand += ` --entrypoint="" `
|
|
|
|
|
|
}
|
2025-08-21 21:31:51 +08:00
|
|
|
|
//创建并运行容器的命令
|
|
|
|
|
|
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
2025-09-13 17:00:58 +08:00
|
|
|
|
Output: "",
|
|
|
|
|
|
Status: "waitting",
|
|
|
|
|
|
UserId: newDevcontainer.UserId,
|
|
|
|
|
|
RepoId: newDevcontainer.RepoId,
|
2025-10-12 23:06:34 +08:00
|
|
|
|
Command: startCommand + imageName + overrideCommand + "\n",
|
2025-08-21 21:31:51 +08:00
|
|
|
|
ListId: 2,
|
|
|
|
|
|
DevcontainerId: newDevcontainer.Id,
|
|
|
|
|
|
}); err != nil {
|
|
|
|
|
|
log.Info("Failed to insert record: %v", err)
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return imageName, err
|
2025-08-21 21:31:51 +08:00
|
|
|
|
}
|
2025-10-12 23:06:34 +08:00
|
|
|
|
|
2025-08-21 21:31:51 +08:00
|
|
|
|
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
2025-10-12 23:06:34 +08:00
|
|
|
|
Output: "",
|
|
|
|
|
|
Status: "waitting",
|
|
|
|
|
|
UserId: newDevcontainer.UserId,
|
|
|
|
|
|
RepoId: newDevcontainer.RepoId,
|
2025-10-16 23:41:01 +08:00
|
|
|
|
Command: `docker -H ` + dockerSocket + ` start -a ` + newDevcontainer.Name + "\n",
|
2025-08-21 21:31:51 +08:00
|
|
|
|
ListId: 3,
|
|
|
|
|
|
DevcontainerId: newDevcontainer.Id,
|
|
|
|
|
|
}); err != nil {
|
|
|
|
|
|
log.Info("Failed to insert record: %v", err)
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return imageName, err
|
2025-08-21 21:31:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
//连接容器的命令
|
|
|
|
|
|
if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
|
|
|
|
|
|
Output: "",
|
|
|
|
|
|
Status: "waitting",
|
|
|
|
|
|
UserId: newDevcontainer.UserId,
|
|
|
|
|
|
RepoId: newDevcontainer.RepoId,
|
2025-10-19 01:10:47 +08:00
|
|
|
|
Command: `docker -H ` + dockerSocket + ` exec -it --workdir ` + newDevcontainer.DevcontainerWorkDir + "/" + repo.Name + ` ` + newDevcontainer.Name + ` sh -c "echo '$WEB_TERMINAL_HELLO';bash"` + "\n",
|
2025-08-21 21:31:51 +08:00
|
|
|
|
ListId: 4,
|
|
|
|
|
|
DevcontainerId: newDevcontainer.Id,
|
|
|
|
|
|
}); err != nil {
|
|
|
|
|
|
log.Info("Failed to insert record: %v", err)
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return imageName, err
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
2025-09-17 11:05:42 +08:00
|
|
|
|
return imageName, nil
|
2025-08-16 18:31:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
func IsContainerNotFound(ctx context.Context, containerName string) (bool, error) {
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 检查是否为 "未找到" 错误
|
|
|
|
|
|
if errdefs.IsNotFound(err) {
|
|
|
|
|
|
return true, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
_, err = cli.ContainerInspect(ctx, containerID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为 "未找到" 错误
|
|
|
|
|
|
if docker_module.IsContainerNotFound(err) {
|
|
|
|
|
|
return true, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
// 其他类型的错误
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 无错误表示容器存在
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
}
|
2025-09-19 12:50:54 +08:00
|
|
|
|
func DeleteDevContainerByDocker(ctx context.Context, devContainerName string) error {
|
2025-08-16 18:31:14 +08:00
|
|
|
|
// 创建docker client
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
// 获取容器 ID
|
|
|
|
|
|
|
2025-09-19 12:50:54 +08:00
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, devContainerName)
|
2025-08-16 18:31:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errdefs.IsNotFound(err) {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 删除容器
|
|
|
|
|
|
if err := docker_module.DeleteContainer(ctx, cli, containerID); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-09-19 12:50:54 +08:00
|
|
|
|
func RestartDevContainerByDocker(ctx context.Context, devContainerName string) error {
|
2025-08-16 18:31:14 +08:00
|
|
|
|
// 创建docker client
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
// 获取容器 ID
|
|
|
|
|
|
|
2025-09-19 12:50:54 +08:00
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, devContainerName)
|
2025-08-16 18:31:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
// restart容器
|
|
|
|
|
|
timeout := 10 // 超时时间(秒)
|
|
|
|
|
|
err = cli.ContainerRestart(context.Background(), containerID, container.StopOptions{
|
|
|
|
|
|
Timeout: &timeout,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.Info("容器已重启")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-09-29 20:13:44 +08:00
|
|
|
|
func StopDevContainerByDocker(ctx context.Context, devContainerName string) error {
|
2025-08-16 18:31:14 +08:00
|
|
|
|
// 创建docker client
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
|
2025-09-29 20:13:44 +08:00
|
|
|
|
// 获取容器 ID
|
|
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, devContainerName)
|
2025-08-16 18:31:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
// stop容器
|
|
|
|
|
|
timeout := 10 // 超时时间(秒)
|
|
|
|
|
|
err = cli.ContainerStop(context.Background(), containerID, container.StopOptions{
|
|
|
|
|
|
Timeout: &timeout,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.Info("容器已停止")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *repo.Repository, doer *user.User) error {
|
|
|
|
|
|
// 创建docker client
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// update容器
|
|
|
|
|
|
imageRef := updateInfo.RepositoryAddress + "/" + updateInfo.RepositoryUsername + "/" + updateInfo.ImageName
|
|
|
|
|
|
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if updateInfo.SaveMethod == "on" {
|
|
|
|
|
|
// 创建构建上下文(包含Dockerfile的tar包)
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
|
tw := tar.NewWriter(&buf)
|
|
|
|
|
|
defer tw.Close()
|
|
|
|
|
|
// 添加Dockerfile到tar包
|
|
|
|
|
|
dockerfile := "Dockerfile"
|
|
|
|
|
|
dockerfileContent, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+configurationModel.Build.Dockerfile)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
content := []byte(dockerfileContent)
|
|
|
|
|
|
header := &tar.Header{
|
|
|
|
|
|
Name: dockerfile,
|
|
|
|
|
|
Size: int64(len(content)),
|
|
|
|
|
|
Mode: 0644,
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := tw.WriteHeader(header); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := tw.Write(content); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
buildOptions := types.ImageBuildOptions{
|
|
|
|
|
|
Tags: []string{imageRef}, // 镜像标签
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_, err = cli.ImageBuild(
|
|
|
|
|
|
context.Background(),
|
|
|
|
|
|
&buf,
|
|
|
|
|
|
buildOptions,
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Info(err.Error())
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 获取容器 ID
|
|
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 提交容器
|
|
|
|
|
|
_, err = cli.ContainerCommit(ctx, containerID, types.ContainerCommitOptions{Reference: imageRef})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 推送到仓库
|
|
|
|
|
|
dockerHost, err := docker_module.GetDockerSocketPath()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
err = docker_module.PushImage(dockerHost, updateInfo.RepositoryUsername, updateInfo.PassWord, updateInfo.RepositoryAddress, imageRef)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-10-22 15:39:09 +08:00
|
|
|
|
|
2025-08-16 18:31:14 +08:00
|
|
|
|
// 定义正则表达式来匹配 image 字段
|
|
|
|
|
|
re := regexp.MustCompile(`"image"\s*:\s*"([^"]+)"`)
|
|
|
|
|
|
// 使用正则表达式查找并替换 image 字段的值
|
|
|
|
|
|
newConfiguration := re.ReplaceAllString(configurationString, `"image": "`+imageRef+`"`)
|
|
|
|
|
|
err = UpdateDevcontainerConfiguration(newConfiguration, repo, doer)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ImageExists 检查指定镜像是否存在
|
|
|
|
|
|
// 返回值:
|
|
|
|
|
|
// - bool: 镜像是否存在(true=存在,false=不存在)
|
|
|
|
|
|
// - error: 非空表示检查过程中发生错误
|
|
|
|
|
|
func ImageExists(ctx context.Context, imageName string) (bool, error) {
|
|
|
|
|
|
// 创建 Docker 客户端
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err // 其他错误
|
|
|
|
|
|
}
|
|
|
|
|
|
// 获取镜像信息
|
|
|
|
|
|
_, _, err = cli.ImageInspectWithRaw(ctx, imageName)
|
|
|
|
|
|
if err != nil {
|
2025-09-18 20:14:43 +08:00
|
|
|
|
if client.IsErrNotFound(err) {
|
|
|
|
|
|
return false, nil // 镜像不存在,但不是错误
|
|
|
|
|
|
}
|
2025-08-16 18:31:14 +08:00
|
|
|
|
return false, err // 其他错误
|
|
|
|
|
|
}
|
|
|
|
|
|
return true, nil // 镜像存在
|
|
|
|
|
|
}
|
2025-10-16 23:41:01 +08:00
|
|
|
|
|
2025-08-16 18:31:14 +08:00
|
|
|
|
func CheckDirExistsFromDocker(ctx context.Context, containerName, dirPath string) (bool, error) {
|
|
|
|
|
|
// 上下文
|
|
|
|
|
|
// 创建 Docker 客户端
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 获取容器 ID
|
|
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 创建 exec 配置
|
|
|
|
|
|
execConfig := types.ExecConfig{
|
|
|
|
|
|
Cmd: []string{"test", "-d", dirPath}, // 检查目录是否存在
|
2025-10-12 23:06:34 +08:00
|
|
|
|
AttachStdout: true,
|
|
|
|
|
|
AttachStderr: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
// 创建 exec 实例
|
|
|
|
|
|
execResp, err := cli.ContainerExecCreate(context.Background(), containerID, execConfig)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
var exitCode int
|
|
|
|
|
|
err = cli.ContainerExecStart(context.Background(), execResp.ID, types.ExecStartCheck{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取命令执行结果
|
|
|
|
|
|
resp, err := cli.ContainerExecInspect(context.Background(), execResp.ID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
exitCode = resp.ExitCode
|
|
|
|
|
|
return exitCode == 0, nil // 退出码为 0 表示目录存在
|
|
|
|
|
|
}
|
2025-10-22 15:39:09 +08:00
|
|
|
|
|
2025-10-12 23:06:34 +08:00
|
|
|
|
func CheckFileExistsFromDocker(ctx context.Context, containerName, filePath string) (bool, error) {
|
|
|
|
|
|
// 上下文
|
|
|
|
|
|
// 创建 Docker 客户端
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 获取容器 ID
|
|
|
|
|
|
containerID, err := docker_module.GetContainerID(cli, containerName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 创建 exec 配置
|
|
|
|
|
|
execConfig := types.ExecConfig{
|
2025-10-16 23:41:01 +08:00
|
|
|
|
Cmd: []string{"test", "-e", filePath}, // 检查文件是否存在
|
2025-08-16 18:31:14 +08:00
|
|
|
|
AttachStdout: true,
|
|
|
|
|
|
AttachStderr: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建 exec 实例
|
|
|
|
|
|
execResp, err := cli.ContainerExecCreate(context.Background(), containerID, execConfig)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
var exitCode int
|
|
|
|
|
|
err = cli.ContainerExecStart(context.Background(), execResp.ID, types.ExecStartCheck{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取命令执行结果
|
|
|
|
|
|
resp, err := cli.ContainerExecInspect(context.Background(), execResp.ID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
exitCode = resp.ExitCode
|
|
|
|
|
|
return exitCode == 0, nil // 退出码为 0 表示目录存在
|
|
|
|
|
|
}
|
2025-08-29 16:08:02 +08:00
|
|
|
|
func RegistWebTerminal(ctx context.Context) error {
|
|
|
|
|
|
log.Info("开始构建WebTerminal...")
|
|
|
|
|
|
cli, err := docker_module.CreateDockerClient(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
//拉取web_terminal镜像
|
|
|
|
|
|
dockerHost, err := docker_module.GetDockerSocketPath()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("获取docker socket路径失败:%v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 拉取镜像
|
|
|
|
|
|
err = docker_module.PullImage(ctx, cli, dockerHost, setting.DevContainerConfig.Web_Terminal_Image)
|
|
|
|
|
|
if err != nil {
|
2025-10-22 19:17:47 +08:00
|
|
|
|
fmt.Errorf("拉取web_terminal镜像失败:%v", err)
|
2025-08-29 16:08:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
timestamp := time.Now().Format("20060102150405")
|
|
|
|
|
|
binds := []string{
|
|
|
|
|
|
"/var/run/docker.sock:/var/run/docker.sock",
|
|
|
|
|
|
}
|
|
|
|
|
|
containerName := "webterminal-" + timestamp
|
|
|
|
|
|
//创建并启动WebTerminal容器
|
|
|
|
|
|
err = docker_module.CreateAndStartContainer(ctx, cli, setting.DevContainerConfig.Web_Terminal_Image,
|
2025-09-04 10:48:46 +08:00
|
|
|
|
nil,
|
2025-08-29 16:08:02 +08:00
|
|
|
|
nil, binds,
|
|
|
|
|
|
nat.PortSet{
|
|
|
|
|
|
"7681/tcp": struct{}{},
|
|
|
|
|
|
},
|
|
|
|
|
|
containerName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建并注册WebTerminal失败:%v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// Save settings.
|
|
|
|
|
|
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-09-13 17:00:58 +08:00
|
|
|
|
_, err = docker_module.GetMappedPort(ctx, containerName, "7681")
|
2025-08-29 16:08:02 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-09-13 17:00:58 +08:00
|
|
|
|
cfg.Section("devcontainer").Key("WEB_TERMINAL_CONTAINER").SetValue(containerName)
|
2025-08-29 16:08:02 +08:00
|
|
|
|
if err = cfg.SaveTo(setting.CustomConf); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|