Files
devstar/services/devcontainer/devcontainer.go

1056 lines
30 KiB
Go

package devcontainer
import (
"archive/tar"
"bytes"
"context"
"fmt"
"math"
"net"
"net/url"
"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/templates"
"code.gitea.io/gitea/modules/setting"
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
}
}
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, doer *user.User, repo *repo.Repository) (OutputResponse, error) {
var devContainerOutput []devcontainer_models.DevcontainerOutput
dbEngine := db.GetEngine(ctx)
resp := OutputResponse{}
var status string
var containerName string
_, err := dbEngine.
Table("devcontainer").
Select("devcontainer_status, name").
Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID).
Get(&status, &containerName)
if err != nil {
return resp, err
}
err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID).
Find(&devContainerOutput)
if err != nil {
return resp, err
}
if len(devContainerOutput) > 0 {
resp.CurrentJob.Title = repo.Name + " Devcontainer Info"
resp.CurrentJob.Detail = status
if status == "4" {
// 获取WebSSH服务端口
webTerminalURL, err := GetWebTerminalURL(ctx, doer.ID, repo.ID)
if err == nil {
return resp, err
}
// 解析URL
u, err := url.Parse(webTerminalURL)
if err != nil {
return resp, err
}
// 分离主机和端口
terminalHost, terminalPort, err := net.SplitHostPort(u.Host)
resp.CurrentJob.IP = terminalHost
resp.CurrentJob.Port = terminalPort
if err != nil {
return resp, err
}
}
for _, item := range devContainerOutput {
logLines := []ViewStepLogLine{}
logLines = append(logLines, ViewStepLogLine{
Index: 1,
Message: item.Output,
})
resp.CurrentJob.Steps = append(resp.CurrentJob.Steps, &ViewJobStep{
Summary: item.Command,
Status: item.Status,
Logs: logLines,
})
}
}
return resp, 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 := onCreateCommand + "\n" + updateCommand + "\n" + postCreateCommand + "\n" + postStartCommand + "\n" + scriptCommand
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), `$`, `$$`)
final_command = re1.ReplaceAllString(final_command, escapedContentStart+"\n"+userCommand)
re1 = regexp.MustCompile(`\$RESTART\b`)
escapedContentRestart := strings.ReplaceAll(string(Content_restart), `$`, `$$`)
final_command = re1.ReplaceAllString(final_command, escapedContentRestart+"\n"+postStartCommand)
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
}