Files
devstar/services/devcontainer/devcontainer_api.go
xinitx cd856c72bc !62 json管理和日志输出
* 合并输出按阶段显示
* ttyd初始目录
* 访问数据库放在services层
* 端口指定映射
* vscode链接
* 去掉devstar字符串
* Devcontainer前端页面显示进行了整理优化
* 修复 数据库 bug
* 增加容器output
2025-03-18 15:52:08 +00:00

200 lines
6.9 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 (
"context"
"fmt"
"regexp"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/setting"
gitea_web_context "code.gitea.io/gitea/services/context"
devcontainer_service_errors "code.gitea.io/gitea/services/devcontainer/errors"
)
// CreateDevcontainerAPIService API专用创建 DevContainer Service
func CreateDevcontainerAPIService(ctx *gitea_web_context.Context, opts *CreateDevcontainerOptions) error {
// 0. 检查用户传入参数
if ctx == nil || opts == nil || opts.Actor == nil || opts.RepoId <= 0 {
return devcontainer_service_errors.ErrIllegalParams{
FieldNameList: []string{"ctx", "opts", "opts.Actor", "opts.RepoId"},
}
}
// sanitize user-input SSH Public Key List
regexpInvalidSSHPublicKey := regexp.MustCompile(`[\r]`)
for _, publicKey := range opts.SSHPublicKeyList {
if len(publicKey) <= 4 || publicKey[0:4] != "ssh-" || regexpInvalidSSHPublicKey.MatchString(publicKey) {
// 遇到可能无效的 SSH Public Key或者导致 k8s Operator YAML 解码失败的字符: `\r`,报错返回
// ERROR Reconciler error:
// {
// "controller": "devcontainerapp",
// "controllerGroup": "devcontainer.devstar.cn",
// "controllerKind": "DevcontainerApp",
// "DevcontainerApp": {
// "name": "leviyanx-16-4834a4c88c4511ef9c1a4e1bce2a7080",
// "namespace": "devstar-studio-ns"
// },
// "namespace": "devstar-studio-ns",
// "name": "leviyanx-16-4834a4c88c4511ef9c1a4e1bce2a7080",
// "reconcileID": "6af51347-7aae-4542-a5cf-2f9b57a202e6",
// "error": "panic: error converting YAML to JSON: yaml: line 46: could not find expected ':' [recovered]"
// }
return devcontainer_service_errors.ErrIllegalParams{
FieldNameList: []string{"SSHPublicKeyList"},
}
}
}
// 1. 开启事务
errTxn := db.WithTx(ctx, func(ctx context.Context) error {
// 1.1 调用 model层查询数据库将 repoId 变换为 Repository 对象
repositoryInDB, err := repo.GetRepositoryByID(ctx, opts.RepoId)
if err != nil || repositoryInDB == nil {
return devcontainer_service_errors.ErrIllegalParams{
FieldNameList: []string{"opts.RepoId"},
}
}
// 1.2 检查该用户在该仓库 是否已经创建过 DevContainer
optsRepoDevcontainer := &RepoDevcontainerOptions{
Actor: opts.Actor,
Repository: repositoryInDB,
}
devcontainerDetails, err := GetRepoDevcontainerDetails(ctx, optsRepoDevcontainer)
if err != nil || devcontainerDetails.DevContainerId > 0 {
return devcontainer_service_errors.ErrDevcontainerAlreadyCreated{
Actor: opts.Actor,
Repository: repositoryInDB,
}
}
// 1.3 调用 DevContainer Service 创建 DevContainer
optsCreateDevcontainer := &CreateRepoDevcontainerOptions{
Actor: opts.Actor,
Repository: repositoryInDB,
SSHPublicKeyList: opts.SSHPublicKeyList,
}
return CreateRepoDevcontainer(ctx, optsCreateDevcontainer)
})
return errTxn
}
// OpenDevcontainerAPIService API 专用获取 DevContainer Service
func OpenDevcontainerAPIService(ctx *gitea_web_context.Context, opts *AbstractOpenDevcontainerOptions) (*RepoDevContainer, error) {
// 0. 检查用户传入参数
if ctx == nil || opts == nil || opts.Actor == nil || opts.RepoId <= 0 {
return nil, devcontainer_service_errors.ErrIllegalParams{
FieldNameList: []string{"ctx", "opts", "opts.Actor", "opts.RepoId"},
}
}
var devcontainerDetails RepoDevContainer
// 1. 开启数据库事务,查询 某用户在某仓库的 DevContainer
errTxn := db.WithTx(ctx, func(ctx context.Context) error {
// 1.1 调用 model层查询数据库将 repoId 变换为 Repository 对象
repositoryInDB, err := repo.GetRepositoryByID(ctx, opts.RepoId)
if err != nil || repositoryInDB == nil {
return devcontainer_service_errors.ErrIllegalParams{
FieldNameList: []string{"opts.RepoId"},
}
}
// 1.2 检查该用户在该仓库 是否已经创建过 DevContainer
optsRepoDevcontainer := &RepoDevcontainerOptions{
Actor: opts.Actor,
Repository: repositoryInDB,
}
devcontainerDetails, err = GetRepoDevcontainerDetails(ctx, optsRepoDevcontainer)
if err != nil || devcontainerDetails.DevContainerId <= 0 {
return devcontainer_service_errors.ErrDevcontainerNotFound{
Actor: opts.Actor,
Repository: repositoryInDB,
}
}
// 1.3 查询 DevContainer 成功,结束数据库事务
return nil
})
if errTxn != nil {
return nil, errTxn
}
// 2. 调用抽象层获取 DevContainer 最新状态(需要根据用户传入的 wait 参数决定是否要阻塞等待 DevContainer 就绪)
optsOpenDevcontainer := &OpenDevcontainerAppDispatcherOptions{
RepoID: opts.RepoId,
UserID: opts.Actor.ID,
Name: devcontainerDetails.DevContainerName,
Port: devcontainerDetails.DevContainerPort,
Wait: opts.Wait,
UserPublicKey: opts.UserPublicKey,
}
openDevcontainerAbstractAgentVO, err := OpenDevcontainerService(ctx, optsOpenDevcontainer)
if err != nil {
return nil, err
}
// 3. 更新VO合并成为真正的 SSH连接信息
devcontainerDetails.DevContainerPort = openDevcontainerAbstractAgentVO.NodePortAssigned
return &devcontainerDetails, nil
}
func UpdateDevcontainerAPIService(ctx *gitea_web_context.Context, opts *UpdateDevcontainerOptions) error {
switch setting.Devcontainer.Agent {
case setting.KUBERNETES:
//k8s处理
return fmt.Errorf("暂时不支持的Agent")
case setting.DOCKER:
return SaveDevcontainer(ctx, opts)
default:
return fmt.Errorf("不支持的Agent")
//默认处理
}
}
// DeleteDevcontainerAPIService API 专用删除 DevContainer Service
func DeleteDevcontainerAPIService(ctx *gitea_web_context.Context, opts *AbstractDeleteDevcontainerOptions) error {
// 0. 检查用户传入参数
if ctx == nil || opts == nil || opts.Actor == nil || opts.RepoId <= 0 {
return devcontainer_service_errors.ErrIllegalParams{
FieldNameList: []string{"ctx", "opts", "opts.Actor", "opts.RepoId"},
}
}
// 1. 开启数据库事务,查询 某用户在某仓库的 DevContainer
errTxn := db.WithTx(ctx, func(ctx context.Context) error {
// 1.1 调用 model层查询数据库将 repoId 变换为 Repository 对象
repositoryInDB, err := repo.GetRepositoryByID(ctx, opts.RepoId)
if err != nil || repositoryInDB == nil {
return devcontainer_service_errors.ErrIllegalParams{
FieldNameList: []string{"opts.RepoId"},
}
}
// 1.2 直接尝试删除 DevContainer若报错则说明不存在 DevContainer
optsRepoDevcontainer := &RepoDevcontainerOptions{
Actor: opts.Actor,
Repository: repositoryInDB,
}
err = DeleteRepoDevcontainer(ctx, optsRepoDevcontainer)
if err != nil {
return devcontainer_service_errors.ErrDevcontainerNotFound{
Actor: opts.Actor,
Repository: repositoryInDB,
}
}
// 1.3 查询 DevContainer 成功,结束数据库事务
return nil
})
// 2. 返回删除结果
return errTxn
}