Files
devstar/services/devstar_devcontainer/RepoDevcontainerService.go
戴明辰 d44bd153c2 !4 完成Repo DevContainer 增、删、查
Merge pull request !4 from 戴明辰/feature-repo-devcontainer-panel
2024-08-30 12:28:59 +00:00

202 lines
8.7 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 devstar_devcontainer
import (
"code.gitea.io/gitea/models/db"
devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer"
"code.gitea.io/gitea/modules/log"
DevcontainersVO "code.gitea.io/gitea/routers/web/devcontainer/vo"
"context"
"fmt"
"github.com/google/uuid"
"regexp"
"strings"
"xorm.io/builder"
)
// GetRepoDevcontainerDetails 获取仓库对应 DevContainer 信息
func GetRepoDevcontainerDetails(ctx context.Context, opts *DevcontainersVO.SearchRepoDevcontainerDetailsVoOptions) (DevcontainersVO.RepoDevcontainerDetailVO, error) {
// 0. 构造异常返回时候的空数据
resultRepoDevcontainerDetail := DevcontainersVO.RepoDevcontainerDetailVO{}
// 1. 检查参数是否有效
if opts == nil || len(opts.UserName) == 0 || len(opts.RepoName) == 0 {
return resultRepoDevcontainerDetail, devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: "construct query condition for devContainer user list",
Message: "invalid search condition",
}
}
// 2. 查询数据库
/*
SELECT devstar_devcontainer.id AS devcontainer_id, devstar_devcontainer.name AS devcontainer_name, devstar_devcontainer.devcontainer_host AS devcontainer_host, devstar_devcontainer.devcontainer_port AS devcontainer_port, devstar_devcontainer.devcontainer_username AS devcontainer_username, devstar_devcontainer.devcontainer_password AS devcontainer_password, devstar_devcontainer.devcontainer_work_dir AS devcontainer_work_dir, devstar_devcontainer.repo_id AS repo_id, repository.name AS repo_name, repository.description AS repo_description
FROM devstar_devcontainer
INNER JOIN repository on devstar_devcontainer.repo_id = repository.id
WHERE
devstar_devcontainer.user_id IN ( SELECT id FROM user WHERE name = '#{opts.UserName}')
AND
devstar_devcontainer.repo_id IN ( SELECT id FROM repository WHERE name = '#{opts.RepoName}');
*/
_, err := db.GetEngine(ctx).
Table("devstar_devcontainer").
Select("devstar_devcontainer.repo_id AS repo_id, repository.name AS repo_name, repository.description AS repo_description, devstar_devcontainer.id AS devcontainer_id, devstar_devcontainer.name AS devcontainer_name, devstar_devcontainer.devcontainer_host AS devcontainer_host, devstar_devcontainer.devcontainer_port AS devcontainer_port, devstar_devcontainer.devcontainer_username AS devcontainer_username, devstar_devcontainer.devcontainer_password AS devcontainer_password, devstar_devcontainer.devcontainer_work_dir AS devcontainer_work_dir").
Join("INNER", "repository", "devstar_devcontainer.repo_id = repository.id").
Where("devstar_devcontainer.user_id IN ( SELECT id FROM user WHERE name = ?) AND devstar_devcontainer.repo_id IN ( SELECT id FROM repository WHERE name = ?)", opts.UserName, opts.RepoName).
Get(&resultRepoDevcontainerDetail)
// 3. 返回
if err != nil {
return resultRepoDevcontainerDetail, devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: fmt.Sprintf("query devcontainer with repo '%v' and username '%v'", opts.RepoName, opts.UserName),
Message: err.Error(),
}
}
return resultRepoDevcontainerDetail, nil
}
// CreateRepoDevcontainer 创建 DevContainer
/*
必要假设:前置中间件已完成检查,确保数据有效:
- 当前用户为已登录用户
- 当前用户拥有 repo code写入权限
- 数据库此前不存在 该用户在该repo创建的 Dev Container
*/
func CreateRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.CreateRepoDevcontainerOptions) error {
// TODO: 调用 k8s client 创建
// 目前只是 mock数据
username := opts.Actor.Name
repoName := opts.Repository.Name
newDevcontainer := &devstar_devcontainer_models.DevstarDevcontainer{
Name: getSanitizedDevcontainerName(username, repoName),
DevcontainerHost: "127.0.0.1",
DevcontainerPort: 10086,
DevcontainerUsername: "mock-username",
DevcontainerPassword: "mock-password",
DevcontainerWorkDir: "~/workspace/",
RepoId: opts.Repository.ID,
UserId: opts.Actor.ID,
}
dbTransactionErr := db.WithTx(ctx, func(ctx context.Context) error {
rowsAffect, err := db.GetEngine(ctx).
Table("devstar_devcontainer").
Insert(newDevcontainer)
if err != nil {
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
Message: err.Error(),
}
} else if rowsAffect == 0 {
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: fmt.Sprintf("insert new DevContainer for user '%s' in repo '%s'", username, repoName),
Message: "expected 1 row to be inserted, but got 0",
}
}
err = claimDevcontainerResource(newDevcontainer)
if err != nil {
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: fmt.Sprintf("Failed to claim resource for Dev Container devstar_devcontainer.DevstarDevcontainer %v", newDevcontainer),
Message: err.Error(),
}
}
return nil
})
return dbTransactionErr
}
// DeleteRepoDevcontainer 按照 仓库 和/或 用户信息删除 DevContainer(s)
func DeleteRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.DeleteRepoDevcontainerOptions) error {
if ctx == nil || opts == nil || (opts.Actor == nil && opts.Repository == nil) {
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: "construct query parameters",
Message: "Invalid parameters",
}
}
// 1. 构造查询条件
sqlDevcontainerCondition := builder.NewCond()
if opts.Actor != nil {
sqlDevcontainerCondition = sqlDevcontainerCondition.And(builder.Eq{"user_id": opts.Actor.ID})
}
if opts.Repository != nil {
sqlDevcontainerCondition = sqlDevcontainerCondition.And(builder.Eq{"repo_id": opts.Repository.ID})
}
var devcontainersList []devstar_devcontainer_models.DevstarDevcontainer
// 2. 开启事务:先获取 devcontainer列表后删除
dbTransactionErr := db.WithTx(ctx, func(ctx context.Context) error {
var err error
// 2.1 条件查询: user_id 和/或 repo_id
err = db.GetEngine(ctx).
Table("devstar_devcontainer").
Where(sqlDevcontainerCondition).
Find(&devcontainersList)
if err != nil {
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: fmt.Sprintf("find devcontainer(s) with condition '%v'", sqlDevcontainerCondition),
Message: err.Error(),
}
}
// 2.2 条件删除: user_id 和/或 repo_id
_, err = db.GetEngine(ctx).
Table("devstar_devcontainer").
Where(sqlDevcontainerCondition).
Delete()
if err != nil {
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: fmt.Sprintf("MARK devcontainer(s) as DELETED with condition '%v'", sqlDevcontainerCondition),
Message: err.Error(),
}
}
return nil
})
if dbTransactionErr != nil {
return dbTransactionErr
}
// 3. 后台启动一个goroutine慢慢回收 Dev Container 资源 (如果回收失败,将会产生孤儿 Dev Container只能管理员手动识别、删除)
go func() {
_ = purgeDevcontainersResource(devcontainersList)
}()
return dbTransactionErr
}
// getSanitizedDevcontainerName 辅助获取当前用户在当前仓库创建的 devContainer名称
// DevContainer命名规则 用户名、仓库名各由小于15位的小写字母和数字组成中间使用'-'分隔,后面使用'-'分隔符拼接32位UUID小写字母数字字符串
func getSanitizedDevcontainerName(username, repoName string) string {
regexpNonAlphaNum := regexp.MustCompile(`[^a-zA-Z0-9]`)
sanitizedUsername := regexpNonAlphaNum.ReplaceAllString(username, "")
sanitizedRepoName := regexpNonAlphaNum.ReplaceAllString(repoName, "")
if len(sanitizedUsername) > 15 {
sanitizedUsername = strings.ToLower(sanitizedUsername[:15])
}
if len(sanitizedRepoName) > 15 {
sanitizedRepoName = strings.ToLower(sanitizedRepoName[:15])
}
newUUID, _ := uuid.NewUUID()
uuidStr := newUUID.String()
uuidStr = regexpNonAlphaNum.ReplaceAllString(uuidStr, "")
return fmt.Sprintf("%s-%s-%s", sanitizedUsername, sanitizedRepoName, uuidStr)
}
// purgeDevcontainersResource 辅助函数用于goroutine后台执行回收DevContainer资源 // TODO
func purgeDevcontainersResource(devcontainersList []devstar_devcontainer_models.DevstarDevcontainer) error {
for _, devContainer := range devcontainersList {
log.Info("[Goroutine purgeDevcontainersResource]: 正在装模做样地回收资源: devstar_devcontainer_models.DevstarDevcontainer %v", devContainer)
}
return nil
}
// claimDevcontainerResource
func claimDevcontainerResource(newDevContainer *devstar_devcontainer_models.DevstarDevcontainer) error {
log.Info("[claimDevcontainerResource]: 正在装模做样地分配资源: devstar_devcontainer_models.DevstarDevcontainer %v", newDevContainer)
return nil
}