From e4859301244d69ed87c3df71cd97fc0b1a89ed38 Mon Sep 17 00:00:00 2001 From: init Date: Wed, 29 Oct 2025 22:43:10 +0800 Subject: [PATCH] fix bug --- docker/Dockerfile.webTerminal | 1 - routers/web/devcontainer/devcontainer.go | 25 ++- routers/web/repo/editor.go | 19 +++ services/devcontainer/devcontainer.go | 145 +++++++++++------- services/devcontainer/docker_agent.go | 80 +++++++++- templates/repo/devcontainer/details.tmpl | 40 ++--- .../repo/devcontainer/devcontainer_tmpl.sh | 1 + 7 files changed, 219 insertions(+), 92 deletions(-) diff --git a/docker/Dockerfile.webTerminal b/docker/Dockerfile.webTerminal index bd54ed9c57..0abfd86c1e 100644 --- a/docker/Dockerfile.webTerminal +++ b/docker/Dockerfile.webTerminal @@ -37,7 +37,6 @@ RUN apt-get update && \ apt remove --purge curl -y && apt autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ENTRYPOINT ["/usr/bin/tini", "--"] -ENV WEB_TERMINAL_HELLO="Successfully connected to the devcontainer" CMD ["/home/webTerminal/build/ttyd", "-W", "bash"] # To acquire devstar.cn/devstar/webterminal:latest: diff --git a/routers/web/devcontainer/devcontainer.go b/routers/web/devcontainer/devcontainer.go index 95a1a3c870..fd0bdc6258 100644 --- a/routers/web/devcontainer/devcontainer.go +++ b/routers/web/devcontainer/devcontainer.go @@ -55,21 +55,22 @@ func GetDevContainerDetails(ctx *context.Context) { ctx.Data["ValidateDevContainerConfiguration"] = false } - ctx.Data["HasDevContainerDockerfile"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo) + ctx.Data["HasDevContainerDockerfile"], ctx.Data["DockerfilePath"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo) if err != nil { log.Info(err.Error()) ctx.Flash.Error(err.Error(), true) } if ctx.Data["HasDevContainer"] == true { - configurationString, _ := devcontainer_service.GetDevcontainerConfigurationString(ctx, ctx.Repo.Repository) - configurationModel, _ := devcontainer_service.UnmarshalDevcontainerConfigContent(configurationString) - imageName := configurationModel.Image - registry, namespace, repo, tag := devcontainer_service.ParseImageName(imageName) - log.Info("%v %v", repo, tag) - ctx.Data["RepositoryAddress"] = registry - ctx.Data["RepositoryUsername"] = namespace - ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest" - + if ctx.Data["HasDevContainerConfiguration"] == true { + configurationString, _ := devcontainer_service.GetDevcontainerConfigurationString(ctx, ctx.Repo.Repository) + configurationModel, _ := devcontainer_service.UnmarshalDevcontainerConfigContent(configurationString) + imageName := configurationModel.Image + registry, namespace, repo, tag := devcontainer_service.ParseImageName(imageName) + log.Info("%v %v", repo, tag) + ctx.Data["RepositoryAddress"] = registry + ctx.Data["RepositoryUsername"] = namespace + ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest" + } if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // 获取WebSSH服务端口 webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) @@ -111,7 +112,6 @@ func GetDevContainerDetails(ctx *context.Context) { } ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer")) } else { - rootPort, err := devcontainer_service.GetPortFromURL(cfg.Section("server").Key("ROOT_URL").Value()) if err != nil { ctx.Flash.Error(err.Error(), true) @@ -136,7 +136,6 @@ func GetDevContainerDetails(ctx *context.Context) { } ctx.Data["WebSSHUrl"] = webTerminalURL + "?type=docker&" + terminalParams } - } terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, ctx.Doer, ctx.Repo) if err == nil { @@ -299,7 +298,7 @@ func UpdateDevContainer(ctx *context.Context) { ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()}) return } - err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo.Repository, &updateInfo) + err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo, &updateInfo) if err != nil { ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()}) return diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 2a5ac10282..b63b990116 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -11,6 +11,7 @@ import ( "path" "strings" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unit" @@ -25,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context/upload" + devcontainer_service "code.gitea.io/gitea/services/devcontainer" "code.gitea.io/gitea/services/forms" files_service "code.gitea.io/gitea/services/repository/files" ) @@ -411,6 +413,23 @@ func DeleteFilePost(ctx *context.Context) { editorHandleFileOperationError(ctx, parsed.NewBranchName, err) return } + log.Info("File deleted: %s", treePath) + if treePath == `.devcontainer/devcontainer.json` { + var userIds []int64 + err = db.GetEngine(ctx). + Table("devcontainer"). + Select("user_id"). + Where("repo_id = ?", ctx.Repo.Repository.ID). + Find(&userIds) + if err != nil { + ctx.ServerError("GetEngine", err) + return + } + for _, userId := range userIds { + devcontainer_service.DeleteDevContainer(ctx, userId, ctx.Repo.Repository.ID) + } + + } ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath)) redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath) diff --git a/services/devcontainer/devcontainer.go b/services/devcontainer/devcontainer.go index 2ba4e6ef7f..8f521e1789 100644 --- a/services/devcontainer/devcontainer.go +++ b/services/devcontainer/devcontainer.go @@ -68,21 +68,21 @@ func HasDevContainerConfiguration(ctx context.Context, repo *gitea_context.Repos return true, nil } } -func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Repository) (bool, error) { +func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Repository) (bool, string, error) { _, err := FileExists(".devcontainer/devcontainer.json", repo) if err != nil { if git.IsErrNotExist(err) { - return false, nil + return false, "", nil } - return false, err + return false, "", err } configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository) if err != nil { - return false, err + return false, "", err } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { - return false, err + return false, "", err } // 执行验证 if errs := configurationModel.Validate(); len(errs) > 0 { @@ -90,20 +90,34 @@ func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Reposito for _, err := range errs { fmt.Printf(" - %s\n", err.Error()) } - return false, fmt.Errorf("配置格式错误") + return false, "", fmt.Errorf("配置格式错误") } else { log.Info("%v", configurationModel) if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" { - return false, nil + _, err := FileExists(".devcontainer/Dockerfile", repo) + if err != nil { + if git.IsErrNotExist(err) { + return false, "", nil + } + return false, "", err + } + return true, ".devcontainer/Dockerfile", nil } _, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo) if err != nil { if git.IsErrNotExist(err) { - return false, nil + _, err := FileExists(".devcontainer/Dockerfile", repo) + if err != nil { + if git.IsErrNotExist(err) { + return false, "", nil + } + return false, "", err + } + return true, ".devcontainer/Dockerfile", nil } - return false, err + return false, "", err } - return true, nil + return true, ".devcontainer/" + configurationModel.Build.Dockerfile, nil } } func CreateDevcontainerConfiguration(repo *repo.Repository, doer *user.User) error { @@ -433,7 +447,7 @@ func StopDevContainer(ctx context.Context, userID, repoID int64) error { return nil } -func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.Repository, updateInfo *UpdateInfo) error { +func UpdateDevContainer(ctx context.Context, doer *user.User, repo *gitea_context.Repository, updateInfo *UpdateInfo) error { dbEngine := db.GetEngine(ctx) var devContainerInfo devcontainer_models.Devcontainer cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) @@ -443,13 +457,13 @@ func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.Reposit _, err = dbEngine. Table("devcontainer"). Select("*"). - Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID). + Where("user_id = ? AND repo_id = ?", doer.ID, repo.Repository.ID). Get(&devContainerInfo) if err != nil { return err } _, err = dbEngine.Table("devcontainer"). - Where("user_id = ? AND repo_id = ? ", doer.ID, repo.ID). + Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 5}) if err != nil { return err @@ -460,7 +474,7 @@ func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.Reposit } else { updateErr := UpdateDevContainerByDocker(otherCtx, &devContainerInfo, updateInfo, repo, doer) _, err = dbEngine.Table("devcontainer"). - Where("user_id = ? AND repo_id = ? ", doer.ID, repo.ID). + Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4}) if err != nil { return err @@ -531,57 +545,72 @@ func GetTerminalCommand(ctx context.Context, userID string, repo *repo.Repositor return "", "", err } } - } break case 2: //正在创建容器,创建容器成功,则状态转移 if cfg.Section("k8s").Key("ENABLE").Value() == "true" { //k8s的逻辑 + } else { - status, err := GetDevContainerStatusFromDocker(ctx, devContainerInfo.Name) + exist, _, err := ContainerExists(ctx, devContainerInfo.Name) if err != nil { return "", "", err } - if status == "created" { - //添加脚本文件 - if cfg.Section("k8s").Key("ENABLE").Value() == "true" { - } else { - userNum, err := strconv.ParseInt(userID, 10, 64) - if err != nil { - return "", "", err - } - var scriptContent string - scriptContent, err = GetCommandContent(ctx, userNum, repo) - log.Info("command: %s", scriptContent) - if err != nil { - return "", "", err - } - // 创建 tar 归档文件 - var buf bytes.Buffer - tw := tar.NewWriter(&buf) - defer tw.Close() - // 添加文件到 tar 归档 - AddFileToTar(tw, "webTerminal.sh", string(scriptContent), 0777) - // 创建 Docker 客户端 - cli, err := docker_module.CreateDockerClient(ctx) - if err != nil { - return "", "", err - } - // 获取容器 ID - containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name) - if err != nil { - return "", "", err - } - err = cli.CopyToContainer(ctx, containerID, "/home", bytes.NewReader(buf.Bytes()), types.CopyToContainerOptions{}) - if err != nil { - log.Info("%v", err) - return "", "", err - } + if !exist { + _, err = dbEngine.Table("devcontainer_output"). + Select("command"). + Where("user_id = ? AND repo_id = ? AND list_id = ?", userID, repo.ID, realTimeStatus). + Get(&cmd) + if err != nil { + return "", "", err } - realTimeStatus = 3 + } else { + status, err := GetDevContainerStatusFromDocker(ctx, devContainerInfo.Name) + if err != nil { + return "", "", err + } + if status == "created" { + //添加脚本文件 + if cfg.Section("k8s").Key("ENABLE").Value() == "true" { + } else { + userNum, err := strconv.ParseInt(userID, 10, 64) + if err != nil { + return "", "", err + } + var scriptContent string + scriptContent, err = GetCommandContent(ctx, userNum, repo) + log.Info("command: %s", scriptContent) + if err != nil { + return "", "", err + } + // 创建 tar 归档文件 + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + defer tw.Close() + // 添加文件到 tar 归档 + AddFileToTar(tw, "webTerminal.sh", string(scriptContent), 0777) + // 创建 Docker 客户端 + cli, err := docker_module.CreateDockerClient(ctx) + if err != nil { + return "", "", err + } + // 获取容器 ID + containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name) + if err != nil { + return "", "", err + } + err = cli.CopyToContainer(ctx, containerID, "/home", bytes.NewReader(buf.Bytes()), types.CopyToContainerOptions{}) + if err != nil { + log.Info("%v", err) + return "", "", err + } + } + realTimeStatus = 3 + } } + } break case 3: @@ -665,6 +694,16 @@ func GetDevContainerOutput(ctx context.Context, user_id string, repo *repo.Repos if err != nil { return "", err } + if devContainerOutput != "" { + _, err = dbEngine.Table("devcontainer_output"). + Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4). + Update(map[string]interface{}{ + "output": "", + }) + if err != nil { + return "", err + } + } return devContainerOutput, nil } @@ -936,7 +975,6 @@ func GetCommandContent(ctx context.Context, userId int64, repo *repo.Repository) script = append(script, v) } scriptCommand := strings.TrimSpace(strings.Join(script, "\n")) - userCommand := scriptCommand + "\n" + onCreateCommand + "\n" + updateCommand + "\n" + postCreateCommand + "\n" + postStartCommand + "\n" assetFS := templates.AssetFS() Content_tmpl, err := assetFS.ReadFile("repo/devcontainer/devcontainer_tmpl.sh") @@ -988,6 +1026,7 @@ func AddPublicKeyToAllRunningDevContainer(ctx context.Context, userId int64, pub if err != nil { return err } + if len(devcontainerList) > 0 { // 将公钥写入这些打开的容器中 for _, repoDevContainer := range devcontainerList { diff --git a/services/devcontainer/docker_agent.go b/services/devcontainer/docker_agent.go index 058ff3e701..5d44318a46 100644 --- a/services/devcontainer/docker_agent.go +++ b/services/devcontainer/docker_agent.go @@ -16,10 +16,13 @@ import ( "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/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + gitea_context "code.gitea.io/gitea/services/context" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" "github.com/docker/docker/errdefs" "github.com/docker/go-connections/nat" @@ -213,7 +216,8 @@ func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *dev var envFlags string = ` -e RepoLink="` + strings.TrimSuffix(cfg.Section("server").Key("ROOT_URL").Value(), `/`) + repo.Link() + `" ` + ` -e DevstarHost="` + newDevcontainer.DevcontainerHost + `"` + ` -e WorkSpace="` + newDevcontainer.DevcontainerWorkDir + `/` + repo.Name + `" ` + - ` -e DEVCONTAINER_STATUS="start" ` + ` -e DEVCONTAINER_STATUS="start" ` + + ` -e WEB_TERMINAL_HELLO="Successfully connected to the devcontainer" ` // 遍历 ContainerEnv 映射中的每个环境变量 for name, value := range configurationModel.ContainerEnv { // 将每个环境变量转换为 "-e name=value" 格式 @@ -391,7 +395,7 @@ func StopDevContainerByDocker(ctx context.Context, devContainerName string) erro } return nil } -func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *repo.Repository, doer *user.User) error { +func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *gitea_context.Repository, doer *user.User) error { // 创建docker client cli, err := docker_module.CreateDockerClient(ctx) if err != nil { @@ -401,7 +405,7 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai // update容器 imageRef := updateInfo.RepositoryAddress + "/" + updateInfo.RepositoryUsername + "/" + updateInfo.ImageName - configurationString, err := GetDevcontainerConfigurationString(ctx, repo) + configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository) if err != nil { return err } @@ -411,16 +415,45 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai } if updateInfo.SaveMethod == "on" { + // 创建构建上下文(包含Dockerfile的tar包) var buf bytes.Buffer tw := tar.NewWriter(&buf) defer tw.Close() // 添加Dockerfile到tar包 + var dockerfileContent string dockerfile := "Dockerfile" - dockerfileContent, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+configurationModel.Build.Dockerfile) - if err != nil { - return err + if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" { + _, err := FileExists(".devcontainer/Dockerfile", repo) + if err != nil { + return err + } + dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/Dockerfile") + if err != nil { + return err + } + } else { + _, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo) + if err != nil { + if git.IsErrNotExist(err) { + _, err := FileExists(".devcontainer/Dockerfile", repo) + if err != nil { + return err + } + dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/Dockerfile") + if err != nil { + return err + } + } + return err + } else { + dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/"+configurationModel.Build.Dockerfile) + if err != nil { + return err + } + } } + content := []byte(dockerfileContent) header := &tar.Header{ Name: dockerfile, @@ -473,7 +506,7 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai re := regexp.MustCompile(`"image"\s*:\s*"([^"]+)"`) // 使用正则表达式查找并替换 image 字段的值 newConfiguration := re.ReplaceAllString(configurationString, `"image": "`+imageRef+`"`) - err = UpdateDevcontainerConfiguration(newConfiguration, repo, doer) + err = UpdateDevcontainerConfiguration(newConfiguration, repo.Repository, doer) if err != nil { return err } @@ -632,3 +665,36 @@ func RegistWebTerminal(ctx context.Context) error { } return nil } + +// ContainerExists 检查容器是否存在,返回存在状态和容器ID(如果存在) +func ContainerExists(ctx context.Context, containerName string) (bool, string, error) { + cli, err := docker_module.CreateDockerClient(ctx) + if err != nil { + return false, "", err + } + // 设置过滤器,根据容器名称过滤 + filter := filters.NewArgs() + filter.Add("name", containerName) + + // 获取容器列表,使用过滤器 + containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{ + All: true, // 包括所有容器(运行的和停止的) + Filters: filter, + }) + if err != nil { + return false, "", err + } + + // 遍历容器,检查名称是否完全匹配 + for _, container := range containers { + for _, name := range container.Names { + // 容器名称在Docker API中是以斜杠开头的,例如 "/my-container" + // 所以我们需要检查去掉斜杠后的名称是否匹配 + if strings.TrimPrefix(name, "/") == containerName { + return true, container.ID, nil + } + } + } + + return false, "", nil +} diff --git a/templates/repo/devcontainer/details.tmpl b/templates/repo/devcontainer/details.tmpl index 3948f676e0..874d97e558 100644 --- a/templates/repo/devcontainer/details.tmpl +++ b/templates/repo/devcontainer/details.tmpl @@ -22,6 +22,7 @@ {{else}}
+
+ {{if and .ValidateDevContainerConfiguration .HasDevContainer}} + {{end}}
{{end}}
@@ -47,7 +50,7 @@ {{ctx.Locale.Tr "repo.dev_container_control"}}
- {{if .HasDevContainer}} + {{if and .ValidateDevContainerConfiguration .HasDevContainer}} {{if .isAdmin}} @@ -106,24 +109,14 @@
{{template "base/modal_actions_confirm" .}} - +