!12 [Feature][Fix][Doc] repo devcontainer.json parser, WeChat QR Expiration Mask, Zombie Process Mitigation

* [Fix] Zombie/Defunct Processes caused by SSH Daemon
* [Fix]  WeChat QR Expiration Mask with a refresh button
* [Doc] Updated API response field `publicKeySsh` of GET `/api/devstar_ssh/key_pair/new_temp`
* [Feature] Get or default DevContainer image from `.devcontainer/devcontainer.json` parser
This commit is contained in:
戴明辰
2024-10-28 11:35:35 +00:00
repo.diff.parent e73016cfe7
repo.diff.commit 6967ba9889
repo.diff.stats_desc%!(EXTRA int=8, int=153, int=27)

repo.diff.view_file

@@ -1,20 +1,24 @@
package devstar_devcontainer
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"code.gitea.io/gitea/models/db"
devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer"
repo_model "code.gitea.io/gitea/models/repo"
git_module "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
DevcontainersVO "code.gitea.io/gitea/routers/api/devcontainer/vo"
devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto"
devcontainer_service_errors "code.gitea.io/gitea/services/devstar_devcontainer/errors"
devcontainer_k8s_agent_service "code.gitea.io/gitea/services/devstar_devcontainer/k8s_agent"
"context"
"fmt"
"github.com/google/uuid"
"regexp"
"strings"
"time"
"xorm.io/builder"
)
@@ -106,6 +110,7 @@ func CreateRepoDevcontainer(ctx context.Context, opts *DevcontainersVO.CreateRep
CreatedUnix: unixTimestamp,
UpdatedUnix: unixTimestamp,
},
Image: GetDefaultDevcontainerImageFromRepoDevcontainerJSON(ctx, opts.Repository),
GitRepositoryURL: strings.TrimSuffix(setting.AppURL, "/") + opts.Repository.Link(),
}
@@ -262,7 +267,7 @@ func getSanitizedDevcontainerName(username, repoName string) string {
// purgeDevcontainersResource 辅助函数用于goroutine后台执行回收DevContainer资源
func purgeDevcontainersResource(ctx *context.Context, devcontainersList *[]devstar_devcontainer_models.DevstarDevcontainer) error {
// 1. 检查 DevContainer 功能是否启用,若禁用,则直接结束,不会真正执行删除操作
if setting.Devstar.Devcontainer.Enabled == false {
if !setting.Devstar.Devcontainer.Enabled {
// 如果用户设置禁用 DevContainer无法删除资源会直接忽略而数据库相关记录会继续清空、不会发生回滚
log.Warn("Orphan DevContainers in namespace `%s` left undeleted: %v", setting.Devstar.Devcontainer.Namespace, devcontainersList)
return nil
@@ -283,7 +288,7 @@ func purgeDevcontainersResource(ctx *context.Context, devcontainersList *[]devst
// claimDevcontainerResource 分发创建 DevContainer 任务到配置文件指定的执行器
func claimDevcontainerResource(ctx *context.Context, newDevContainer *devcontainer_service_dto.CreateDevcontainerDTO) error {
// 1. 检查 DevContainer 功能是否启用,若禁用,则直接结束
if setting.Devstar.Devcontainer.Enabled == false {
if !setting.Devstar.Devcontainer.Enabled {
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
Action: "Check for DevContainer functionality switch",
Message: "DevContainer is disabled globally, please check your configuration files",
@@ -303,3 +308,52 @@ func claimDevcontainerResource(ctx *context.Context, newDevContainer *devcontain
}
}
}
// GetDefaultDevcontainerImageFromRepoDevcontainerJSON 从 .devcontainer/devcontainer.json 中获取 devContainer image
func GetDefaultDevcontainerImageFromRepoDevcontainerJSON(ctx context.Context, repo *repo_model.Repository) string {
// 1. 获取默认分支名
branchName := repo.DefaultBranch
if len(branchName) == 0 {
branchName = setting.Devstar.Devcontainer.DefaultGitBranchName
}
// 2. 打开默认分支
gitRepoEntity, err := git_module.OpenRepository(ctx, repo.RepoPath())
if err != nil {
log.Error("Failed to open repository %s: %v", repo.RepoPath(), err)
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
}
defer func(gitRepoEntity *git_module.Repository) {
_ = gitRepoEntity.Close()
}(gitRepoEntity)
// 3. 获取分支名称
commit, err := gitRepoEntity.GetBranchCommit(branchName)
if err != nil {
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
}
// 4. 读取 .devcontainer/devcontainer.json 文件
const maxDevcontainerJSONSize = 10 * 1024 * 1024 // 设置最大允许的文件大小 1MB
devcontainerJSONContent, err := commit.GetFileContent(".devcontainer/devcontainer.json", maxDevcontainerJSONSize)
if err != nil {
log.Error("Failed to get .devcontainer/devcontainer.json file: %v", err)
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
}
// 5. 解析 JSON
var devcontainerJSON map[string]interface{}
err = json.Unmarshal([]byte(devcontainerJSONContent), &devcontainerJSON)
if err != nil {
log.Error("Failed to unmarshal .devcontainer/devcontainer.json: %v", err)
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
}
// 6. 解析并返回
devcontainerImage, ok := devcontainerJSON["image"].(string)
if !ok || len(devcontainerImage) == 0 {
return setting.Devstar.Devcontainer.DefaultDevcontainerImageName
}
return devcontainerImage
}

repo.diff.view_file

@@ -8,4 +8,5 @@ type CreateDevcontainerDTO struct {
devstar_devcontainer_models.DevstarDevcontainer
SSHPublicKeyList []string
GitRepositoryURL string
Image string
}

repo.diff.view_file

@@ -1,10 +1,11 @@
package k8s_agent
import (
"context"
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
"code.gitea.io/gitea/modules/setting"
devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto"
"context"
apimachinery_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
devcontainer_k8s_agent_module "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent"
@@ -31,12 +32,29 @@ func AssignDevcontainerCreation2K8sOperator(ctx *context.Context, newDevContaine
CreateOptions: apimachinery_meta_v1.CreateOptions{},
Name: newDevContainer.Name,
Namespace: setting.Devstar.Devcontainer.Namespace,
// TODO: 后期根据 .devcontainer/devcontainer.json 或默认设置选项,指定 devContainer image 和 初始化命令
Image: "devstar.cn/public/base-ssh-devcontainer:ubuntu-20.04-20241014",
Image: newDevContainer.Image,
/**
* 配置 Kubernetes 主容器启动命令注意事项:
* 1. 确保 Image 中已安装 OpenSSH Server
* 2. 容器启动后必须拉起 OpenSSH 后台服务
* 3. 请勿使用 sleep infinity 或者 tail -f /dev/null 等无限等待命令,否则将造成大量僵尸(<defunct>)进程:
* $ ps aux | grep "<defunct>" # 列举僵尸进程
* USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
* pollina+ 2336 0.0 0.0 0 0 ? Z 17:22 0:00 [sshd] <defunct>
* pollina+ 10986 0.0 0.0 0 0 ? Z 16:12 0:00 [sshd] <defunct>
* pollina+ 24722 0.0 0.0 0 0 ? Z 18:36 0:00 [sshd] <defunct>
* pollina+ 26773 0.0 0.0 0 0 ? Z 18:37 0:00 [sshd] <defunct>
* $ ubuntu@node2:~$ ps o ppid 2336 10986 24722 26773 # 查询僵尸进程父进程PID
* PPID
* 21826
* $ ps aux | grep # 列举僵尸进程父进程详情
* USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
* root 21826 0.0 0.0 2520 408 ? Ss 18:36 0:00 sleep infinity
*/
CommandList: []string{
"/bin/bash",
"-c",
"service ssh restart && sleep infinity",
"service ssh restart",
},
ContainerPort: 22,
ServicePort: 22,