package devcontainer import ( "archive/tar" "bytes" "context" "fmt" "math" "regexp" "strconv" "strings" "time" auth_model "code.gitea.io/gitea/models/auth" "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/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" gitea_context "code.gitea.io/gitea/services/context" files_service "code.gitea.io/gitea/services/repository/files" "github.com/docker/docker/api/types" "xorm.io/builder" ) func HasDevContainer(ctx context.Context, userID, repoID int64) (bool, error) { var hasDevContainer bool dbEngine := db.GetEngine(ctx) hasDevContainer, err := dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", userID, repoID). Exist() if err != nil { return hasDevContainer, err } return hasDevContainer, nil } func HasDevContainerConfiguration(ctx context.Context, repo *gitea_context.Repository) (bool, error) { _, err := FileExists(".devcontainer/devcontainer.json", repo) if err != nil { if git.IsErrNotExist(err) { return false, nil } return false, err } configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository) if err != nil { return true, err } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { return true, err } // 执行验证 if errs := configurationModel.Validate(); len(errs) > 0 { log.Info("配置验证失败:") for _, err := range errs { fmt.Printf(" - %s\n", err.Error()) } return true, fmt.Errorf("配置格式错误") } else { return true, nil } } 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, "", err } configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository) if err != nil { return false, "", err } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { return false, "", err } // 执行验证 if errs := configurationModel.Validate(); len(errs) > 0 { log.Info("配置验证失败:") for _, err := range errs { fmt.Printf(" - %s\n", err.Error()) } return false, "", fmt.Errorf("配置格式错误") } else { log.Info("%v", configurationModel) if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" { _, 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) { _, 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 true, ".devcontainer/" + configurationModel.Build.Dockerfile, nil } } func CreateDevcontainerConfiguration(repo *repo.Repository, doer *user.User) error { jsonContent, err := templates.AssetFS().ReadFile("repo/devcontainer/default_devcontainer.json") if err != nil { return err } _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, doer, &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ { Operation: "create", TreePath: ".devcontainer/devcontainer.json", ContentReader: bytes.NewReader([]byte(jsonContent)), }, }, OldBranch: "main", NewBranch: "main", Message: "add container configuration", }) if err != nil { return err } return nil } func GetWebTerminalURL(ctx context.Context, userID, repoID int64) (string, error) { var devcontainerName string cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return "", err } dbEngine := db.GetEngine(ctx) _, err = dbEngine. Table("devcontainer"). Select("name"). Where("user_id = ? AND repo_id = ?", userID, repoID). Get(&devcontainerName) if err != nil { return "", err } if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // K8s 模式:使用 Istio Gateway + VirtualService log.Info("GetWebTerminalURL: 使用 Istio 模式获取 WebTerminal URL for DevContainer: %s", devcontainerName) // 从配置中读取域名 domain := cfg.Section("server").Key("DOMAIN").Value() // 从容器名称中提取用户名和仓库名 parts := strings.Split(devcontainerName, "-") var username, repoName string if len(parts) >= 2 { username = parts[0] repoName = parts[1] } else { username = "unknown" repoName = "unknown" } // 构建基于 Istio Gateway 的 URL path := fmt.Sprintf("/%s/%s/dev-container-webterminal", username, repoName) webTerminalURL := fmt.Sprintf("http://%s%s", domain, path) log.Info("GetWebTerminalURL: 生成 Istio WebTerminal URL: %s", webTerminalURL) return webTerminalURL, nil } return "", nil } /* -1不存在 0 已创建数据库记录 1 正在拉取镜像 2 正在创建和启动容器 3 容器安装必要工具 4 容器正在运行 5 正在提交容器更新 6 正在重启 7 正在停止 8 容器已停止 9 正在删除 10已删除 */ // checkContainerExistence 检测容器是否存在 // 返回 true 表示容器存在,false 表示容器不存在 func checkContainerExistence(ctx context.Context, containerName string) (bool, error) { cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return false, err } if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // K8s 模式:检查 Pod 是否存在 opts := &OpenDevcontainerAppDispatcherOptions{ Name: containerName, Wait: false, } devcontainerApp, err := AssignDevcontainerGetting2K8sOperator(&ctx, opts) return (err == nil && devcontainerApp != nil), err } else { // Docker 模式:检查容器是否存在 isContainerNotFound, err := IsContainerNotFound(ctx, containerName) if err != nil { return false, err } return !isContainerNotFound, nil } } func GetDevContainerStatus(ctx context.Context, userID, repoID string) (string, error) { log.Info("GetDevContainerStatus: Starting - userID: %s, repoID: %s", userID, repoID) var id int var containerName string var status uint16 var realTimeStatus uint16 cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { log.Error("GetDevContainerStatus: Failed to load config: %v", err) return "", err } dbEngine := db.GetEngine(ctx) _, err = dbEngine. Table("devcontainer"). Select("devcontainer_status, id, name"). Where("user_id = ? AND repo_id = ?", userID, repoID). Get(&status, &id, &containerName) if err != nil { log.Error("GetDevContainerStatus: Failed to query database: %v", err) return "", err } log.Info("GetDevContainerStatus: Database query result - id: %d, containerName: %s, status: %d", id, containerName, status) if id == 0 { log.Info("GetDevContainerStatus: No devcontainer found, returning -1") return fmt.Sprintf("%d", -1), nil } realTimeStatus = status log.Info("GetDevContainerStatus: Initial realTimeStatus: %d", realTimeStatus) // 统一检查容器是否已被删除 // 如果容器不存在,直接设置为已删除状态,跳过后续的状态检测 // 注意:创建过程中的状态(0,1,2,3)和正在删除的状态(9)不应该检测容器是否存在 if status != 0 && status != 1 && status != 2 && status != 3 && status != 9 { containerExists, err := checkContainerExistence(ctx, containerName) if err != nil { return "", err } if !containerExists { // 容器不存在,已被删除 log.Info("GetDevContainerStatus: Container %s not found, considering deleted", containerName) realTimeStatus = 10 // 已删除 // 直接更新数据库并返回 _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Delete() if err != nil { return "", err } _, err = dbEngine.Table("devcontainer_output"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Delete() if err != nil { return "", err } return "-1", nil } } switch status { //正在重启 case 6: if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // k8s 逻辑:检查 Pod 是否已恢复运行 log.Info("GetDevContainerStatus: K8s branch for case 6 (restarting), container: %s", containerName) opts := &OpenDevcontainerAppDispatcherOptions{ Name: containerName, Wait: false, } log.Info("GetDevContainerStatus: Calling AssignDevcontainerGetting2K8sOperator with opts: %+v", opts) devcontainerApp, err := AssignDevcontainerGetting2K8sOperator(&ctx, opts) if err != nil { log.Error("GetDevContainerStatus: AssignDevcontainerGetting2K8sOperator failed: %v", err) } else if devcontainerApp != nil { log.Info("GetDevContainerStatus: DevcontainerApp retrieved - Name: %s, Ready: %v", devcontainerApp.Name, devcontainerApp.Status.Ready) if devcontainerApp.Status.Ready { realTimeStatus = 4 // 已恢复运行 log.Info("GetDevContainerStatus: Container %s is ready, updating status to 4", containerName) } } else { log.Warn("GetDevContainerStatus: DevcontainerApp is nil for container: %s", containerName) } } else { containerRealTimeStatus, err := GetDevContainerStatusFromDocker(ctx, containerName) if err != nil { return "", err } else if containerRealTimeStatus == "running" { realTimeStatus = 4 } } break //正在关闭 case 7: if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // k8s 逻辑:检查 Pod 是否已停止 log.Info("GetDevContainerStatus: K8s branch for case 7 (stopping), container: %s", containerName) opts := &OpenDevcontainerAppDispatcherOptions{ Name: containerName, Wait: false, } log.Info("GetDevContainerStatus: Calling AssignDevcontainerGetting2K8sOperator for stop check with opts: %+v", opts) devcontainerApp, err := AssignDevcontainerGetting2K8sOperator(&ctx, opts) if err != nil { log.Info("GetDevContainerStatus: DevcontainerApp not found or error, considering stopped: %v", err) realTimeStatus = 8 // 已停止 } else if devcontainerApp == nil || !devcontainerApp.Status.Ready { log.Info("GetDevContainerStatus: DevcontainerApp is nil or not ready, considering stopped") realTimeStatus = 8 // 已停止 } else { log.Info("GetDevContainerStatus: DevcontainerApp still running - Name: %s, Ready: %v", devcontainerApp.Name, devcontainerApp.Status.Ready) } // 已在外部通过 StopDevContainer 触发,此处仅检查状态 } else { containerRealTimeStatus, err := GetDevContainerStatusFromDocker(ctx, containerName) if err != nil { return "", err } else if containerRealTimeStatus == "exited" { realTimeStatus = 8 } else { err = StopDevContainerByDocker(ctx, containerName) if err != nil { log.Info(err.Error()) } } } break case 9: if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // k8s 逻辑:检查 Pod 是否已删除 log.Info("GetDevContainerStatus: K8s branch for case 9 (deleting), container: %s", containerName) opts := &OpenDevcontainerAppDispatcherOptions{ Name: containerName, Wait: false, } log.Info("GetDevContainerStatus: Calling AssignDevcontainerGetting2K8sOperator for delete check with opts: %+v", opts) _, err := AssignDevcontainerGetting2K8sOperator(&ctx, opts) if err != nil { log.Info("GetDevContainerStatus: DevcontainerApp not found, considering deleted: %v", err) realTimeStatus = 10 // 已删除 } else { log.Info("GetDevContainerStatus: DevcontainerApp still exists, not deleted yet") } // 已在外部通过 DeleteDevContainer 触发,此处仅检查状态 } else { isContainerNotFound, err := IsContainerNotFound(ctx, containerName) if err != nil { return "", err } else if isContainerNotFound { realTimeStatus = 10 } else { err = DeleteDevContainerByDocker(ctx, containerName) if err != nil { log.Info(err.Error()) } } } break default: log.Info("other status") } // K8s: 仅在 Ready 后才返回 4;否则维持/降为 3 if cfg.Section("k8s").Key("ENABLE").Value() == "true" && (status == 3 || status == 4) { opts := &OpenDevcontainerAppDispatcherOptions{ Name: containerName, Wait: false, } app, err := AssignDevcontainerGetting2K8sOperator(&ctx, opts) if err != nil || app == nil { // 获取不到 CR 或出错时,保守认为未就绪 realTimeStatus = 3 } else if app.Status.Ready { realTimeStatus = 4 } else { realTimeStatus = 3 } } //状态更新 if realTimeStatus != status { if realTimeStatus == 10 { _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Delete() if err != nil { return "", err } _, err = dbEngine.Table("devcontainer_output"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Delete() if err != nil { return "", err } return "-1", nil } _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: realTimeStatus}) if err != nil { return "", err } _, err = dbEngine.Table("devcontainer_output"). Where("user_id = ? AND repo_id = ? AND list_id = ?", userID, repoID, status). Update(&devcontainer_models.DevcontainerOutput{Status: "finished"}) if err != nil { return "", err } } log.Info("GetDevContainerStatus: Final realTimeStatus: %d, returning status string", realTimeStatus) return fmt.Sprintf("%d", realTimeStatus), nil } func CreateDevContainer(ctx context.Context, repo *repo.Repository, doer *user.User, publicKeyList []string, isWebTerminal bool) error { containerName := getSanitizedDevcontainerName(doer.Name, repo.Name) cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return err } unixTimestamp := time.Now().Unix() newDevcontainer := devcontainer_models.Devcontainer{ Name: containerName, DevcontainerHost: cfg.Section("server").Key("DOMAIN").Value(), DevcontainerUsername: "root", DevcontainerWorkDir: "/workspace", DevcontainerStatus: 0, RepoId: repo.ID, UserId: doer.ID, CreatedUnix: unixTimestamp, UpdatedUnix: unixTimestamp, } dbEngine := db.GetEngine(ctx) _, err = dbEngine. Table("devcontainer"). Insert(newDevcontainer) if err != nil { return err } _, err = dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID). Get(&newDevcontainer) if err != nil { return err } go func() { otherCtx := context.Background() if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // K8s 模式:直接调用 K8s Operator 创建 DevContainer configurationString, err := GetDevcontainerConfigurationString(otherCtx, repo) if err != nil { log.Info("CreateDevContainer: 读取 devcontainer 配置失败: %v", err) return } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { log.Info("CreateDevContainer: 解析 devcontainer 配置失败: %v", err) return } newDTO := &CreateDevcontainerDTO{ Devcontainer: newDevcontainer, SSHPublicKeyList: publicKeyList, GitRepositoryURL: strings.TrimSuffix(setting.AppURL, "/") + repo.Link(), Image: configurationModel.Image, } if err := AssignDevcontainerCreation2K8sOperator(&otherCtx, newDTO); err != nil { log.Error("CreateDevContainer: K8s 创建失败: %v", err) return } } else { imageName, err := CreateDevContainerByDockerCommand(otherCtx, &newDevcontainer, repo, publicKeyList) if err != nil { return } if !isWebTerminal { CreateDevContainerByDockerAPI(otherCtx, &newDevcontainer, imageName, repo, publicKeyList) } } }() return nil } func DeleteDevContainer(ctx context.Context, userID, repoID int64) error { dbEngine := db.GetEngine(ctx) var devContainerInfo devcontainer_models.Devcontainer cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return err } _, err = dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", userID, repoID). Get(&devContainerInfo) if err != nil { return err } _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 9}) if err != nil { return err } go func() { otherCtx := context.Background() if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // k8s 模式:调用 K8s Operator 删除 DevContainer 资源 devList := []devcontainer_models.Devcontainer{devContainerInfo} _ = AssignDevcontainerDeletion2K8sOperator(&otherCtx, &devList) } else { err = DeleteDevContainerByDocker(otherCtx, devContainerInfo.Name) if err != nil { log.Info(err.Error()) } } }() return nil } func RestartDevContainer(ctx context.Context, userID, repoID int64) error { dbEngine := db.GetEngine(ctx) var devContainerInfo devcontainer_models.Devcontainer cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return err } _, err = dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", userID, repoID). Get(&devContainerInfo) if err != nil { return err } _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 6}) if err != nil { return err } go func() { otherCtx := context.Background() if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // k8s 模式:调用 K8s Operator 重启 DevContainer vo := &DevcontainerVO{ DevContainerName: devContainerInfo.Name, UserId: userID, RepoId: repoID, } if err := AssignDevcontainerRestart2K8sOperator(&otherCtx, vo); err != nil { log.Error("RestartDevContainer: K8s 重启失败: %v", err) } } else { err = RestartDevContainerByDocker(otherCtx, devContainerInfo.Name) if err != nil { log.Error("RestartDevContainer: Docker restart call failed: %v", err) // Try to check container current status, if already running then sync database statusStr, stErr := GetDevContainerStatusFromDocker(otherCtx, devContainerInfo.Name) if stErr == nil && statusStr == "running" { _, updateErr := db.GetEngine(otherCtx).Table("devcontainer"). Where("user_id = ? AND repo_id = ?", userID, repoID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4}) if updateErr != nil { log.Error("RestartDevContainer: Failed to update status to 4: %v", updateErr) } else { _, upOutErr := db.GetEngine(otherCtx).Table("devcontainer_output"). Where("user_id = ? AND repo_id = ? AND list_id = ?", userID, repoID, 6). Update(&devcontainer_models.DevcontainerOutput{Status: "finished"}) if upOutErr != nil { log.Error("RestartDevContainer: Failed to update output status: %v", upOutErr) } } } else { log.Error("RestartDevContainer: Container is not running after restart call failed: %v", stErr) } } else { // Restart call succeeded, poll until container status is running or timeout for i := 0; i < 10; i++ { statusStr, stErr := GetDevContainerStatusFromDocker(otherCtx, devContainerInfo.Name) if stErr == nil && statusStr == "running" { _, updateErr := db.GetEngine(otherCtx).Table("devcontainer"). Where("user_id = ? AND repo_id = ?", userID, repoID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4}) if updateErr != nil { log.Error("RestartDevContainer: Failed to update status to 4: %v", updateErr) } else { _, upOutErr := db.GetEngine(otherCtx).Table("devcontainer_output"). Where("user_id = ? AND repo_id = ? AND list_id = ?", userID, repoID, 6). Update(&devcontainer_models.DevcontainerOutput{Status: "finished"}) if upOutErr != nil { log.Error("RestartDevContainer: Failed to update output status: %v", upOutErr) } } break } time.Sleep(2 * time.Second) } } } }() return nil } func StopDevContainer(ctx context.Context, userID, repoID int64) error { dbEngine := db.GetEngine(ctx) var devContainerInfo devcontainer_models.Devcontainer cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return err } _, err = dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", userID, repoID). Get(&devContainerInfo) if err != nil { return err } _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", userID, repoID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 7}) if err != nil { return err } go func() { otherCtx := context.Background() var stopErr error if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // k8s 模式:调用 K8s Operator 停止 DevContainer vo := &DevcontainerVO{ DevContainerName: devContainerInfo.Name, UserId: userID, RepoId: repoID, } stopErr = AssignDevcontainerStop2K8sOperator(&otherCtx, vo) if stopErr != nil { log.Error("StopDevContainer: K8s stop failed: %v", stopErr) } } else { stopErr = StopDevContainerByDocker(otherCtx, devContainerInfo.Name) if stopErr != nil { log.Info("StopDevContainer: Docker stop failed: %v", stopErr) } } // After stop operation completes, update status to 8 (stopped) updateCtx := context.Background() if stopErr == nil { // Stop succeeded, update status to 8 _, updateErr := db.GetEngine(updateCtx).Table("devcontainer"). Where("user_id = ? AND repo_id = ?", userID, repoID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 8}) if updateErr != nil { log.Error("StopDevContainer: Failed to update status: %v", updateErr) } else { log.Info("StopDevContainer: Container stopped, status updated to 8") } } }() return nil } 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) if err != nil { return err } _, err = dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", doer.ID, repo.Repository.ID). Get(&devContainerInfo) if err != nil { return err } // 保存原始状态,以便出错时回滚 originalStatus := devContainerInfo.DevcontainerStatus _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 5}) if err != nil { return err } otherCtx := context.Background() if cfg.Section("k8s").Key("ENABLE").Value() == "true" { //k8s的逻辑 } else { updateErr := UpdateDevContainerByDocker(otherCtx, &devContainerInfo, updateInfo, repo, doer) if updateErr != nil { // 出错时回滚状态 _, rollbackErr := dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: originalStatus}) if rollbackErr != nil { log.Error("Failed to rollback devcontainer status: %v", rollbackErr) } return updateErr } _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4}) if err != nil { return err } } return nil } func GetTerminalCommand(ctx context.Context, userID string, repo *repo.Repository) (string, string, error) { dbEngine := db.GetEngine(ctx) var devContainerInfo devcontainer_models.Devcontainer cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return "", "", err } _, err = dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", userID, repo.ID). Get(&devContainerInfo) if err != nil { return "", "", err } realTimeStatus := devContainerInfo.DevcontainerStatus var cmd string switch devContainerInfo.DevcontainerStatus { case 0: if devContainerInfo.Id > 0 { realTimeStatus = 1 } break case 1: //正在拉取镜像,当镜像拉取成功,则状态转移 if cfg.Section("k8s").Key("ENABLE").Value() == "true" { //k8s的逻辑 } else { configurationString, err := GetDevcontainerConfigurationString(ctx, repo) if err != nil { return "", "", err } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { return "", "", err } var imageName string if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" { imageName = configurationModel.Image } else { imageName = userID + "-" + fmt.Sprintf("%d", repo.ID) + "-dockerfile" } isExist, err := ImageExists(ctx, imageName) if err != nil { return "", "", err } if isExist { realTimeStatus = 2 } else { _, 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 } } } break case 2: //正在创建容器,创建容器成功,则状态转移 if cfg.Section("k8s").Key("ENABLE").Value() == "true" { //k8s的逻辑 } else { exist, _, err := ContainerExists(ctx, devContainerInfo.Name) if err != nil { 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 } } else { status, err := GetDevContainerStatusFromDocker(ctx, devContainerInfo.Name) if err != nil { return "", "", err } // 只要容器存在(无论是 created, exited 还是 running),都视为创建成功,进入下一阶段 if status == "created" || status == "exited" || status == "running" { //添加脚本文件 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: //正在初始化容器,初始化容器成功,则状态转移 if cfg.Section("k8s").Key("ENABLE").Value() == "true" { //k8s的逻辑 } else { status, err := CheckDirExistsFromDocker(ctx, devContainerInfo.Name, devContainerInfo.DevcontainerWorkDir) if err != nil { return "", "", err } if status { realTimeStatus = 4 } } break case 4: //正在连接容器 if cfg.Section("k8s").Key("ENABLE").Value() == "true" { //k8s的逻辑 } else { _, 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 } } break } if realTimeStatus != devContainerInfo.DevcontainerStatus { //下一条指令 _, 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 } _, err = dbEngine.Table("devcontainer"). Where("user_id = ? AND repo_id = ? ", userID, repo.ID). Update(&devcontainer_models.Devcontainer{DevcontainerStatus: realTimeStatus}) if err != nil { return "", "", err } } // 统一在状态为4时追加 postAttachCommand,避免首次从3跳到4时遗漏 if realTimeStatus == 4 && cfg.Section("k8s").Key("ENABLE").Value() != "true" { configurationString, err := GetDevcontainerConfigurationString(ctx, repo) if err != nil { return "", "", err } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { return "", "", err } postAttachCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.PostAttachCommand), "\n")) if _, ok := configurationModel.PostAttachCommand.(map[string]interface{}); ok { cmdObj := configurationModel.PostAttachCommand.(map[string]interface{}) if pathValue, hasPath := cmdObj["path"]; hasPath { fileCommand, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+pathValue.(string)) if err != nil { return "", "", err } postAttachCommand += "\n" + fileCommand } } cmd += postAttachCommand } return cmd, fmt.Sprintf("%d", realTimeStatus), nil } func GetDevContainerOutput(ctx context.Context, user_id string, repo *repo.Repository) (string, error) { var devContainerOutput string dbEngine := db.GetEngine(ctx) _, err := dbEngine.Table("devcontainer_output"). Select("output"). Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4). Get(&devContainerOutput) 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 } func SaveDevContainerOutput(ctx context.Context, user_id string, repo *repo.Repository, newoutput string) error { var devContainerOutput string var finalOutput string dbEngine := db.GetEngine(ctx) // 从数据库中获取现有的输出内容 _, err := dbEngine.Table("devcontainer_output"). Select("output"). Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4). Get(&devContainerOutput) if err != nil { return err } devContainerOutput = strings.TrimSuffix(devContainerOutput, "\r\n") if newoutput == "\b \b" { finalOutput = devContainerOutput[:len(devContainerOutput)-1] } else { finalOutput = devContainerOutput + newoutput } _, err = dbEngine.Table("devcontainer_output"). Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4). Update(map[string]interface{}{ "output": finalOutput + "\r\n", }) if err != nil { return err } return nil } func GetMappedPort(ctx context.Context, containerName string, port string) (uint16, error) { cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { return 0, err } if cfg.Section("k8s").Key("ENABLE").Value() == "true" { //k8s的逻辑 return 0, nil } else { port, err := docker_module.GetMappedPort(ctx, containerName, port) if err != nil { return 0, err } return port, nil } } // GetDevcontainersList 获取 devcontainer 列表(保持向后兼容) func GetDevcontainersList(ctx context.Context, doer *user.User, pageNum, pageSize int) (DevcontainerList, error) { opts := FindDevcontainerOptions{ ListOptions: db.ListOptions{ Page: pageNum, PageSize: pageSize, }, UserID: doer.ID, } return FindDevcontainers(ctx, opts) } // FindDevcontainers 根据选项查询 devcontainer 列表 func FindDevcontainers(ctx context.Context, opts FindDevcontainerOptions) (DevcontainerList, error) { // 0. 构造异常返回时的空数据 var resultDevContainerListVO = DevcontainerList{ Page: 0, PageSize: 50, PageTotalNum: 0, ItemTotalNum: 0, DevContainers: []DevcontainerListItem{}, } // 获取用户信息 if opts.UserID > 0 { doer, err := user.GetUserByID(ctx, opts.UserID) if err != nil { return resultDevContainerListVO, err } resultDevContainerListVO.UserID = doer.ID resultDevContainerListVO.Username = doer.Name } opts.ListAll = false // 强制使用分页查询,禁止一次性列举所有 devContainers if opts.Page <= 0 { // 未指定页码/无效页码:查询第 1 页 opts.Page = 1 } if opts.PageSize <= 0 || opts.PageSize > 50 { opts.PageSize = 50 // /无效页面大小/超过每页最大限制:自动调整到系统最大开发容器页面大小 } resultDevContainerListVO.Page = opts.Page resultDevContainerListVO.PageSize = opts.PageSize // 2. SQL 条件构建 var sqlCondition builder.Cond = builder.NewCond() // 用户ID过滤(0表示不过滤) if opts.UserID > 0 { sqlCondition = sqlCondition.And(builder.Eq{"devcontainer.user_id": opts.UserID}) } // 仓库ID过滤(0表示不过滤) if opts.RepoID > 0 { sqlCondition = sqlCondition.And(builder.Eq{"devcontainer.repo_id": opts.RepoID}) } // 执行数据库事务 err := db.WithTx(ctx, func(ctx context.Context) error { // 组织ID过滤 - 需要先查询组织下的所有仓库 var orgRepoIDs []int64 if opts.OrgID > 0 { var orgRepos []struct { ID int64 `xorm:"id"` } err := db.GetEngine(ctx). Table("repository"). Where("owner_id = ?", opts.OrgID). Select("id"). Find(&orgRepos) if err != nil { return err } for _, r := range orgRepos { orgRepoIDs = append(orgRepoIDs, r.ID) } if len(orgRepoIDs) > 0 { sqlCondition = sqlCondition.And(builder.In("devcontainer.repo_id", orgRepoIDs)) } else { // 组织下没有仓库,返回空结果 return nil } } // 关键词搜索 - 如果有关键词,需要先找到匹配的 repo_id var repoIDs []int64 if opts.Keyword != "" { keywordPattern := "%" + opts.Keyword + "%" // 先搜索匹配的仓库 var matchingRepos []struct { ID int64 `xorm:"id"` } repoQuery := db.GetEngine(ctx).Table("repository").Where("name LIKE ?", keywordPattern) // 如果有组织过滤,只在组织的仓库中搜索 if opts.OrgID > 0 && len(orgRepoIDs) > 0 { repoQuery = repoQuery.In("id", orgRepoIDs) } err := repoQuery.Select("id").Find(&matchingRepos) if err != nil { return err } for _, r := range matchingRepos { repoIDs = append(repoIDs, r.ID) } // 如果找到了匹配的仓库,或者容器名称匹配,则添加到条件中 if len(repoIDs) > 0 { sqlCondition = sqlCondition.And(builder.Or( builder.Like{"devcontainer.name", keywordPattern}, builder.In("devcontainer.repo_id", repoIDs), )) } else { // 只搜索容器名称 sqlCondition = sqlCondition.And(builder.Like{"devcontainer.name", keywordPattern}) } } // 查询总数 countSess := db.GetEngine(ctx). Table("devcontainer"). Where(sqlCondition) count, err := countSess.Count() if err != nil { return err } resultDevContainerListVO.ItemTotalNum = count // 无记录直接返回 if count == 0 { return nil } // 计算分页参数 pageSize := int64(resultDevContainerListVO.PageSize) resultDevContainerListVO.PageTotalNum = int(math.Ceil(float64(count) / float64(pageSize))) // 查询分页数据 - 使用 xorm 查询,但需要确保字段正确映射 // 先查询 devcontainer 数据 var devcontainers []devcontainer_models.Devcontainer devcontainerSess := db.GetEngine(ctx). Table("devcontainer"). Where(sqlCondition). OrderBy("devcontainer.id ASC") err = db.SetSessionPagination(devcontainerSess, &opts.ListOptions). Find(&devcontainers) if err != nil { return err } // 然后为每个 devcontainer 查询仓库信息、动态端口,并检测状态变化 resultDevContainerListVO.DevContainers = make([]DevcontainerListItem, 0, len(devcontainers)) for _, dc := range devcontainers { item := DevcontainerListItem{ Devcontainer: dc, } // 查询仓库信息 repo, repoErr := repo.GetRepositoryByID(ctx, dc.RepoId) if repoErr == nil && repo != nil { item.RepoName = repo.Name item.RepoOwnerName = repo.OwnerName item.RepoLink = "/" + repo.OwnerName + "/" + repo.Name } // 注意:列表查询时不进行状态检测,只显示数据库中的状态 // 状态检测可以通过"刷新状态"按钮手动触发 // 这样可以避免每次列表查询都检测所有容器,提高性能 // 动态获取 SSH 端口(如果容器正在运行) // 尝试获取端口,不管状态如何(因为状态可能不准确) mappedPort, err := GetMappedPort(ctx, dc.Name, "22") if err == nil && mappedPort > 0 { item.DevcontainerPort = mappedPort } else { // 如果获取失败,尝试使用数据库中存储的端口(如果有) if dc.DevcontainerPort > 0 { item.DevcontainerPort = dc.DevcontainerPort } } resultDevContainerListVO.DevContainers = append(resultDevContainerListVO.DevContainers, item) } return nil }) if err != nil { return resultDevContainerListVO, err } return resultDevContainerListVO, nil } func Get_IDE_TerminalURL(ctx *gitea_context.Context, doer *user.User, repo *gitea_context.Repository) (string, error) { dbEngine := db.GetEngine(ctx) var devContainerInfo devcontainer_models.Devcontainer _, err := dbEngine. Table("devcontainer"). Select("*"). Where("user_id = ? AND repo_id = ?", doer.ID, repo.Repository.ID). Get(&devContainerInfo) if err != nil { return "", err } configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository) if err != nil { return "", err } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { return "", err } // 加载配置文件 cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { log.Error("Get_IDE_TerminalURL: 加载配置文件失败: %v", err) return "", err } log.Info("Get_IDE_TerminalURL: 配置文件加载成功, ROOT_URL=%s", cfg.Section("server").Key("ROOT_URL").Value()) var access_token string // 检查 session 中是否已存在 token if ctx.Session.Get("terminal_login_token") != nil { access_token = ctx.Session.Get("terminal_login_token").(string) // 验证 token 是否仍然有效 _, err := auth_model.GetAccessTokenBySHA(ctx, access_token) if err != nil { // token 验证失败,删除 session 中的 token 并重新生成 log.Warn("Get_IDE_TerminalURL: token 验证失败,删除 session 中的 token 并重新生成: %v", err) ctx.Session.Delete("terminal_login_token") access_token = "" // 清空,后续会重新生成 } } // 如果 session 中没有 token,才创建新的 if access_token == "" { // 检查数据库中是否已存在同名 token token := &auth_model.AccessToken{ UID: devContainerInfo.UserId, Name: "terminal_login_token", } exist, err := auth_model.AccessTokenByNameExists(ctx, token) if err != nil { return "", err } // 如果数据库中已存在同名 token,先删除旧的(避免 token 堆积) // 因为 session 中没有 token,说明这些旧 token 可能已经不被使用了 if exist { // 删除所有同名的旧 token _, err = db.GetEngine(ctx).Table("access_token"). Where("uid = ? AND name = ?", doer.ID, "terminal_login_token"). Delete(&auth_model.AccessToken{}) if err != nil { log.Warn("Get_IDE_TerminalURL: 删除旧 token 失败: %v", err) // 继续创建新 token,不因为删除失败而中断 } else { log.Info("Get_IDE_TerminalURL: 已删除旧的 terminal_login_token,创建新 token") } } // 生成新 token scope, err := auth_model.AccessTokenScope(strings.Join([]string{"write:user", "write:repository"}, ",")).Normalize() if err != nil { return "", err } token.Scope = scope err = auth_model.NewAccessToken(db.DefaultContext, token) if err != nil { return "", err } ctx.Session.Set("terminal_login_token", token.Token) access_token = token.Token } // 根据不同的代理类型获取 SSH 端口 var port string if cfg.Section("k8s").Key("ENABLE").Value() == "true" { // K8s 环境:通过 DevcontainerApp 的 NodePort 作为 SSH 端口 apiRequestCtx := ctx.Req.Context() opts := &OpenDevcontainerAppDispatcherOptions{ Name: devContainerInfo.Name, Wait: false, } devcontainerApp, err := AssignDevcontainerGetting2K8sOperator(&apiRequestCtx, opts) if err != nil { return "", err } if devcontainerApp == nil || devcontainerApp.Status.NodePortAssigned == 0 { return "", fmt.Errorf("k8s DevcontainerApp 未就绪或未分配 NodePort: %s", devContainerInfo.Name) } port = fmt.Sprintf("%d", devcontainerApp.Status.NodePortAssigned) } else { mappedPort, err := docker_module.GetMappedPort(ctx, devContainerInfo.Name, "22") if err != nil { return "", err } port = fmt.Sprintf("%d", mappedPort) } // 读取 devcontainer 配置获取 forwardPorts 信息 var forwardPortsValue string if len(configurationModel.ForwardPorts) > 0 { var portStrings []string for _, p := range configurationModel.ForwardPorts { switch v := p.(type) { case float64: portStrings = append(portStrings, fmt.Sprintf("%.0f", v)) case string: portStrings = append(portStrings, v) case int: portStrings = append(portStrings, fmt.Sprintf("%d", v)) default: log.Warn("Get_IDE_TerminalURL: 未知的 forwardPorts 类型: %T", v) } } if len(portStrings) > 0 { forwardPortsValue = strings.Join(portStrings, ",") } } // 构建并返回 URL url := "://mengning.devstar/" + "openProject?host=" + repo.Repository.Name + "&hostname=" + devContainerInfo.DevcontainerHost + "&port=" + port + "&username=" + doer.Name + "&path=" + devContainerInfo.DevcontainerWorkDir + "&access_token=" + access_token + "&devstar_username=" + repo.Repository.OwnerName + "&devstar_domain=" + cfg.Section("server").Key("ROOT_URL").Value() // 添加 forwardPorts 参数(如果存在) if forwardPortsValue != "" { url += "&forwardPorts=" + forwardPortsValue } return url, nil } func GetCommandContent(ctx context.Context, userId int64, repo *repo.Repository) (string, error) { configurationString, err := GetDevcontainerConfigurationString(ctx, repo) if err != nil { return "", err } configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString) if err != nil { return "", err } onCreateCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.OnCreateCommand), "\n")) if _, ok := configurationModel.OnCreateCommand.(map[string]interface{}); ok { // 是 map[string]interface{} 类型 cmdObj := configurationModel.OnCreateCommand.(map[string]interface{}) if pathValue, hasPath := cmdObj["path"]; hasPath { fileCommand, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+pathValue.(string)) if err != nil { return "", err } onCreateCommand += "\n" + fileCommand } } updateCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.UpdateContentCommand), "\n")) if _, ok := configurationModel.UpdateContentCommand.(map[string]interface{}); ok { // 是 map[string]interface{} 类型 cmdObj := configurationModel.UpdateContentCommand.(map[string]interface{}) if pathValue, hasPath := cmdObj["path"]; hasPath { fileCommand, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+pathValue.(string)) if err != nil { return "", err } updateCommand += "\n" + fileCommand } } postCreateCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.PostCreateCommand), "\n")) if _, ok := configurationModel.PostCreateCommand.(map[string]interface{}); ok { // 是 map[string]interface{} 类型 cmdObj := configurationModel.PostCreateCommand.(map[string]interface{}) if pathValue, hasPath := cmdObj["path"]; hasPath { fileCommand, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+pathValue.(string)) if err != nil { return "", err } postCreateCommand += "\n" + fileCommand } } postStartCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.PostStartCommand), "\n")) if _, ok := configurationModel.PostStartCommand.(map[string]interface{}); ok { // 是 map[string]interface{} 类型 cmdObj := configurationModel.PostStartCommand.(map[string]interface{}) if pathValue, hasPath := cmdObj["path"]; hasPath { fileCommand, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+pathValue.(string)) if err != nil { return "", err } postStartCommand += "\n" + fileCommand } } var script []string scripts, err := devcontainer_models.GetScript(ctx, userId, repo.ID) for _, v := range scripts { 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") if err != nil { return "", err } Content_start, err := assetFS.ReadFile("repo/devcontainer/devcontainer_start.sh") if err != nil { return "", err } Content_restart, err := assetFS.ReadFile("repo/devcontainer/devcontainer_restart.sh") if err != nil { return "", err } final_command := string(Content_tmpl) re1 := regexp.MustCompile(`\$\{` + regexp.QuoteMeta("START") + `\}|` + `\$` + regexp.QuoteMeta("START") + `\b`) escapedContentStart := strings.ReplaceAll(string(Content_start), `$`, `$$`) escapedUserCommand := strings.ReplaceAll(userCommand, `$`, `$$`) final_command = re1.ReplaceAllString(final_command, escapedContentStart+"\n"+escapedUserCommand) re1 = regexp.MustCompile(`\$RESTART\b`) escapedContentRestart := strings.ReplaceAll(string(Content_restart), `$`, `$$`) escapedPostStartCommand := strings.ReplaceAll(postStartCommand, `$`, `$$`) final_command = re1.ReplaceAllString(final_command, escapedContentRestart+"\n"+escapedPostStartCommand) return parseCommand(ctx, final_command, userId, repo) } func AddPublicKeyToAllRunningDevContainer(ctx context.Context, userId int64, publicKey string) error { // 加载配置文件 cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { log.Error("Get_IDE_TerminalURL: 加载配置文件失败: %v", err) return err } if cfg.Section("k8s").Key("ENABLE").Value() == "true" { } else { cli, err := docker_module.CreateDockerClient(ctx) if err != nil { return err } defer cli.Close() var devcontainerList []devcontainer_models.Devcontainer // 查询所有打开的容器 err = db.GetEngine(ctx). Table("devcontainer"). Where("user_id = ? AND devcontainer_status = ?", userId, 4). Find(&devcontainerList) if err != nil { return err } if len(devcontainerList) > 0 { // 将公钥写入这些打开的容器中 for _, repoDevContainer := range devcontainerList { containerID, err := docker_module.GetContainerID(cli, repoDevContainer.Name) if err != nil { return err } log.Info("container id: %s, name: %s", containerID, repoDevContainer.Name) // 检查容器状态 containerStatus, err := docker_module.GetContainerStatus(cli, containerID) if err != nil { continue } if containerStatus == "running" { // 只为处于运行状态的容器添加公钥 _, err = docker_module.ExecCommandInContainer(ctx, cli, repoDevContainer.Name, fmt.Sprintf("echo '%s' >> ~/.ssh/authorized_keys", publicKey)) if err != nil { return err } } } } return nil } return fmt.Errorf("unknown agent") } func parseCommand(ctx context.Context, command string, userId int64, repo *repo.Repository) (string, error) { variables, err := devcontainer_models.GetVariables(ctx, userId, repo.ID) var variablesName []string variablesCircle := checkEachVariable(variables) for key := range variables { if !variablesCircle[key] { variablesName = append(variablesName, key) } } for ContainsAnySubstring(command, variablesName) { for key, value := range variables { if variablesCircle[key] == true { continue } log.Info("key: %s, value: %s", key, value) re1 := regexp.MustCompile(`\$\{` + regexp.QuoteMeta(key) + `\}|` + `\$` + regexp.QuoteMeta(key) + `\b`) escapedValue := strings.ReplaceAll(value, `$`, `$$`) command = re1.ReplaceAllString(command, escapedValue) variablesName = append(variablesName, key) } } var userSSHPublicKeyList []string err = db.GetEngine(ctx). Table("public_key"). Select("content"). Where("owner_id = ?", userId). Find(&userSSHPublicKeyList) if err != nil { return "", err } re1 := regexp.MustCompile(`\$\{` + regexp.QuoteMeta("PUBLIC_KEY_LIST") + `\}|` + `\$` + regexp.QuoteMeta("PUBLIC_KEY_LIST") + `\b`) command = re1.ReplaceAllString(command, strings.Join(userSSHPublicKeyList, "\n")) return command, nil }