1056 lines
31 KiB
Go
1056 lines
31 KiB
Go
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"
|
|
"code.gitea.io/gitea/modules/docker"
|
|
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, 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 == "" {
|
|
return false, nil
|
|
}
|
|
_, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo)
|
|
if err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return true, 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的逻辑
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
/*
|
|
-1不存在
|
|
0 已创建数据库记录
|
|
1 正在拉取镜像
|
|
2 正在创建和启动容器
|
|
3 容器安装必要工具
|
|
4 容器正在运行
|
|
5 正在提交容器更新
|
|
6 正在重启
|
|
7 正在停止
|
|
8 容器已停止
|
|
9 正在删除
|
|
10已删除
|
|
*/
|
|
func GetDevContainerStatus(ctx context.Context, userID, repoID string) (string, error) {
|
|
var id int
|
|
var containerName string
|
|
|
|
var status uint16
|
|
var realTimeStatus uint16
|
|
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
|
if err != nil {
|
|
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 {
|
|
return "", err
|
|
}
|
|
|
|
if id == 0 {
|
|
return fmt.Sprintf("%d", -1), nil
|
|
}
|
|
|
|
realTimeStatus = status
|
|
switch status {
|
|
//正在重启
|
|
case 6:
|
|
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
|
//k8s的逻辑
|
|
} 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的逻辑
|
|
} 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的逻辑
|
|
} 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")
|
|
}
|
|
//状态更新
|
|
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
|
|
}
|
|
}
|
|
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的逻辑
|
|
} 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的逻辑
|
|
} 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的逻辑
|
|
} else {
|
|
err = RestartDevContainerByDocker(otherCtx, devContainerInfo.Name)
|
|
if err != nil {
|
|
log.Info(err.Error())
|
|
}
|
|
}
|
|
}()
|
|
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()
|
|
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
|
//k8s的逻辑
|
|
} else {
|
|
err = StopDevContainerByDocker(otherCtx, devContainerInfo.Name)
|
|
if err != nil {
|
|
log.Info(err.Error())
|
|
}
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.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.ID).
|
|
Get(&devContainerInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = dbEngine.Table("devcontainer").
|
|
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.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)
|
|
_, err = dbEngine.Table("devcontainer").
|
|
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.ID).
|
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if updateErr != nil {
|
|
return updateErr
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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:
|
|
//正在初始化容器,初始化容器成功,则状态转移
|
|
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
|
|
}
|
|
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 {
|
|
// 是 map[string]interface{} 类型
|
|
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
|
|
}
|
|
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
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
func GetDevcontainersList(ctx context.Context, doer *user.User, pageNum, pageSize int) (DevcontainerList, error) {
|
|
|
|
// 0. 构造异常返回时的空数据
|
|
var resultDevContainerListVO = DevcontainerList{
|
|
Page: 0,
|
|
PageSize: 50,
|
|
PageTotalNum: 0,
|
|
ItemTotalNum: 0,
|
|
DevContainers: []devcontainer_models.Devcontainer{},
|
|
}
|
|
resultDevContainerListVO.UserID = doer.ID
|
|
resultDevContainerListVO.Username = doer.Name
|
|
|
|
paginationOption := db.ListOptions{
|
|
Page: pageNum,
|
|
PageSize: pageSize,
|
|
}
|
|
|
|
paginationOption.ListAll = false // 强制使用分页查询,禁止一次性列举所有 devContainers
|
|
if paginationOption.Page <= 0 { // 未指定页码/无效页码:查询第 1 页
|
|
paginationOption.Page = 1
|
|
}
|
|
if paginationOption.PageSize <= 0 || paginationOption.PageSize > 50 {
|
|
paginationOption.PageSize = 50 // /无效页面大小/超过每页最大限制:自动调整到系统最大开发容器页面大小
|
|
}
|
|
resultDevContainerListVO.Page = paginationOption.Page
|
|
resultDevContainerListVO.PageSize = paginationOption.PageSize
|
|
|
|
// 2. SQL 条件构建
|
|
|
|
sqlCondition := builder.Eq{"user_id": doer.ID}
|
|
// 执行数据库事务
|
|
err := db.WithTx(ctx, func(ctx context.Context) error {
|
|
// 查询总数
|
|
count, err := db.GetEngine(ctx).
|
|
Table("devcontainer").
|
|
Where(sqlCondition).
|
|
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)))
|
|
|
|
// 查询分页数据
|
|
sess := db.GetEngine(ctx).
|
|
Table("devcontainer").
|
|
Join("INNER", "repository", "devcontainer.repo_id = repository.id").
|
|
Where(sqlCondition).
|
|
OrderBy("devcontainer_id DESC").
|
|
Select(`devcontainer.id AS devcontainer_id,
|
|
devcontainer.name AS devcontainer_name,
|
|
devcontainer.devcontainer_host AS devcontainer_host,
|
|
devcontainer.devcontainer_username AS devcontainer_username,
|
|
devcontainer.devcontainer_work_dir AS devcontainer_work_dir,
|
|
devcontainer.repo_id AS repo_id,
|
|
repository.name AS repo_name,
|
|
repository.owner_name AS repo_owner_name,
|
|
repository.description AS repo_description,
|
|
CONCAT('/', repository.owner_name, '/', repository.name) AS repo_link`)
|
|
|
|
resultDevContainerListVO.DevContainers = make([]devcontainer_models.Devcontainer, 0, pageSize)
|
|
err = db.SetSessionPagination(sess, &paginationOption).
|
|
Find(&resultDevContainerListVO.DevContainers)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 加载配置文件
|
|
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("access_token") != nil {
|
|
access_token = ctx.Session.Get("access_token").(string)
|
|
} else {
|
|
// 生成 token
|
|
token := &auth_model.AccessToken{
|
|
UID: devContainerInfo.UserId,
|
|
Name: "terminal_login_token",
|
|
}
|
|
exist, err := auth_model.AccessTokenByNameExists(ctx, token)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if exist {
|
|
db.GetEngine(ctx).Table("access_token").Where("uid = ? AND name = ?", doer.ID, "terminal_login_token").Delete()
|
|
}
|
|
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" {
|
|
|
|
} else {
|
|
mappedPort, err := docker_module.GetMappedPort(ctx, devContainerInfo.Name, "22")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
port = fmt.Sprintf("%d", mappedPort)
|
|
}
|
|
|
|
// 构建并返回 URL
|
|
return "://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(), 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.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.GetContainerID(cli, repoDevContainer.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Info("container id: %s, name: %s", containerID, repoDevContainer.Name)
|
|
// 检查容器状态
|
|
containerStatus, err := docker.GetContainerStatus(cli, containerID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if containerStatus == "running" {
|
|
// 只为处于运行状态的容器添加公钥
|
|
_, err = docker.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
|
|
}
|