* DELETE /api/devcontainer?repoId=${repoId} 删除 DevContainer
* refactor
* GET /api/devcontainer?repoId=${repoId}&wait=true 阻塞式等待打开就绪的 DevContainer
* POST /api/devcontainer 创建 DevContainer
* refactored the code
* Updated context usage with cancel function
* 预留接口,适配单机版 DevStar DevContainer
* bugFix: context canceled while deleting k8s CRD DevcontainerApp
* 用户界面删除 k8s CRD DevContainer
* 用户界面创建 DevContainer 并更新 NodePort
* 完成用户界面创建 DevContainer
* transplant test code into DevStar Studio
* refactored API router to /routers/api
* 更改 DevContainer Doc
* 更改 DevContainer namespace
* 特殊仓库重定向
* [Doc] 更新 Kubernetes 部署 DevStar Studio 文档说明,特别是 namespace 管理
* [Doc] 更新 CI脚本说明
* Revert "optimized CI workflow"
* optimized CI workflow
* fix typo
* [feature test]: 测试 Pod 内使用 Kubernetes Operator 功能
* [Optimization] error msg for archived repo
* [Optimization]: display detailed err msg on creating devContainer for …
158 lines
6.7 KiB
Go
158 lines
6.7 KiB
Go
package k8s_agent
|
|
|
|
import (
|
|
devcontainer_api_v1 "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/api/v1"
|
|
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
|
devcontainer_errors "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/errors"
|
|
devcontainer_module_utils "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/utils"
|
|
devcontainer_agent_module_vo "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/vo"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"context"
|
|
"fmt"
|
|
apimachinery_api_metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
apimachinery_apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
apimachinery_apis_v1_unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
apimachinery_runtime_utils "k8s.io/apimachinery/pkg/runtime"
|
|
apimachinery_watch "k8s.io/apimachinery/pkg/watch"
|
|
dynamic_client "k8s.io/client-go/dynamic"
|
|
)
|
|
|
|
func GetDevcontainer(ctx *context.Context, client dynamic_client.Interface, opts *devcontainer_dto.GetDevcontainerOptions) (*devcontainer_api_v1.DevcontainerApp, error) {
|
|
|
|
// 0. 检查参数
|
|
if ctx == nil || opts == nil || len(opts.Namespace) == 0 || len(opts.Name) == 0 {
|
|
return nil, devcontainer_errors.ErrIllegalDevcontainerParameters{
|
|
FieldList: []string{"ctx", "opts", "opts.Name", "opts.Namespace"},
|
|
Message: "cannot be nil",
|
|
}
|
|
}
|
|
|
|
// 1. 获取 k8s CRD 资源 DevcontainerApp
|
|
devcontainerUnstructured, err := client.Resource(groupVersionResource).Namespace(opts.Namespace).Get(*ctx, opts.Name, opts.GetOptions)
|
|
if err != nil {
|
|
return nil, devcontainer_errors.ErrOperateDevcontainer{
|
|
Action: "Get DevcontainerApp thru k8s API Server",
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
|
|
// 2. 解析 DevcontainerApp Status 域,装填 VO
|
|
devcontainerApp := &devcontainer_api_v1.DevcontainerApp{}
|
|
err = apimachinery_runtime_utils.DefaultUnstructuredConverter.FromUnstructured(devcontainerUnstructured.Object, &devcontainerApp)
|
|
if err != nil {
|
|
return nil, devcontainer_errors.ErrOperateDevcontainer{
|
|
Action: "Convert k8s API Server unstructured response into DevcontainerApp",
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
|
|
// 3. 检查 Devcontainer 是否就绪
|
|
if !devcontainer_module_utils.IsK8sDevcontainerStatusReady(&devcontainerApp.Status) {
|
|
// 3.1 检查 Wait 参数,若用户不需要阻塞式等待,直接返回 “DevContainer 未就绪” 错误
|
|
if opts.Wait == false {
|
|
return nil, devcontainer_errors.ErrK8sDevcontainerNotReady{
|
|
Name: opts.Name,
|
|
Namespace: opts.Namespace,
|
|
Wait: opts.Wait,
|
|
}
|
|
}
|
|
|
|
// 3.2 执行阻塞式等待
|
|
devcontainerStatusVO, err := waitUntilDevcontainerReadyWithTimeout(ctx, client, opts)
|
|
if err != nil {
|
|
return nil, devcontainer_errors.ErrOperateDevcontainer{
|
|
Action: "wait for k8s DevContainer to be ready",
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
devcontainerApp.Status.Ready = devcontainerStatusVO.Ready
|
|
devcontainerApp.Status.NodePortAssigned = devcontainerStatusVO.NodePortAssigned
|
|
}
|
|
|
|
// 4. 将就绪的 DevContainer Status VO 返回
|
|
return devcontainerApp, nil
|
|
}
|
|
|
|
// waitUntilDevcontainerReadyWithTimeout 辅助方法:在超时时间内阻塞等待 DevContainer 就绪
|
|
func waitUntilDevcontainerReadyWithTimeout(ctx *context.Context, client dynamic_client.Interface, opts *devcontainer_dto.GetDevcontainerOptions) (*devcontainer_agent_module_vo.DevcontainerStatusK8sAgentVO, error) {
|
|
|
|
// 0. 检查参数
|
|
if ctx == nil || client == nil || opts == nil || len(opts.Name) == 0 || len(opts.Namespace) == 0 {
|
|
return nil, devcontainer_errors.ErrIllegalDevcontainerParameters{
|
|
FieldList: []string{"ctx", "client", "opts", "opts.Name", "opts.Namespace"},
|
|
Message: "could not be nil",
|
|
}
|
|
}
|
|
|
|
// 1. 注册 watcher 监听 DevContainer Status 变化
|
|
watcherTimeoutSeconds := setting.Devstar.Devcontainer.TimeoutSeconds
|
|
watcher, err := client.Resource(groupVersionResource).Namespace(opts.Namespace).Watch(*ctx, apimachinery_apis_v1.ListOptions{
|
|
FieldSelector: fmt.Sprintf("metadata.name=%s", opts.Name),
|
|
Watch: true,
|
|
TimeoutSeconds: &watcherTimeoutSeconds,
|
|
})
|
|
if err != nil {
|
|
return nil, devcontainer_errors.ErrOperateDevcontainer{
|
|
Action: "register watcher of DevContainer Readiness",
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
defer watcher.Stop()
|
|
|
|
// 2. 当 DevContainer Watcher 事件处理
|
|
devcontainerStatusVO := &devcontainer_agent_module_vo.DevcontainerStatusK8sAgentVO{}
|
|
for event := range watcher.ResultChan() {
|
|
switch event.Type {
|
|
case apimachinery_watch.Added:
|
|
// 2.1 监听 DevcontainerApp ADDED 事件,直接 fallthrough 到 MODIFIED 事件合并处理
|
|
fallthrough
|
|
case apimachinery_watch.Modified:
|
|
// 2.2 监听 DevcontainerApp MODIFIED 事件
|
|
if devcontainerUnstructured, ok := event.Object.(*apimachinery_apis_v1_unstructured.Unstructured); ok {
|
|
// 2.2.1 解析 status 域
|
|
statusDevcontainer, ok, err := apimachinery_apis_v1_unstructured.NestedMap(devcontainerUnstructured.Object, "status")
|
|
if err == nil && ok {
|
|
devcontainerCurrentStatus := &devcontainer_api_v1.DevcontainerAppStatus{
|
|
Ready: statusDevcontainer["ready"].(bool),
|
|
NodePortAssigned: uint16(statusDevcontainer["nodePortAssigned"].(int64)),
|
|
}
|
|
// 2.2.2 当 Status 达到就绪状态后,返回
|
|
if devcontainer_module_utils.IsK8sDevcontainerStatusReady(devcontainerCurrentStatus) {
|
|
return devcontainerStatusVO, nil
|
|
}
|
|
}
|
|
}
|
|
case apimachinery_watch.Error:
|
|
// 2.3 监听 DevcontainerApp ERROR 事件,返回报错信息
|
|
apimachineryApiMetav1Status, ok := event.Object.(*apimachinery_api_metav1.Status)
|
|
if !ok {
|
|
return nil, devcontainer_errors.ErrOperateDevcontainer{
|
|
Action: fmt.Sprintf("wait for Devcontainer '%s' in namespace '%s' to be ready", opts.Name, opts.Namespace),
|
|
Message: fmt.Sprintf("An error occurred in k8s CRD DevcontainerApp Watcher: \n"+
|
|
" Code: %v (status = %v)\n"+
|
|
"Message: %v\n"+
|
|
" Reason: %v\n"+
|
|
"Details: %v",
|
|
apimachineryApiMetav1Status.Code, apimachineryApiMetav1Status.Status,
|
|
apimachineryApiMetav1Status.Message,
|
|
apimachineryApiMetav1Status.Reason,
|
|
apimachineryApiMetav1Status.Details),
|
|
}
|
|
}
|
|
case apimachinery_watch.Deleted:
|
|
// 2.4 监听 DevcontainerApp DELETED 事件,返回报错信息
|
|
return nil, devcontainer_errors.ErrOperateDevcontainer{
|
|
Action: fmt.Sprintf("Open DevContainer '%s' in namespace '%s'", opts.Name, opts.Namespace),
|
|
Message: fmt.Sprintf("'%s' of Kind DevcontainerApp has been Deleted", opts.Name),
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. k8s CRD DevcontainerApp Watcher 超时关闭处理:直接返回超时错误
|
|
return nil, devcontainer_errors.ErrOpenDevcontainerTimeout{
|
|
Name: opts.Name,
|
|
Namespace: opts.Namespace,
|
|
TimeoutSeconds: setting.Devstar.Devcontainer.TimeoutSeconds,
|
|
}
|
|
}
|