Files
devstar/services/devcontainer/devcontainer.go
hwy 47ea96bff4
Some checks failed
DevStar Studio Auto Test Pipeline / unit-frontend-test (pull_request) Failing after 6m25s
DevStar Studio Auto Test Pipeline / unit-backend-test (pull_request) Failing after 4m54s
DevStar Studio CI/CD Pipeline / build-and-push-x86-64-docker-image (pull_request) Failing after 1m24s
DevStar E2E Test / e2e-test (pull_request) Failing after 3m0s
修改开发容器保存功能,优化前端界面,增加转圈定时状态检测
2025-12-19 21:43:24 +08:00

1536 lines
49 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 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
}