Files
devstar/modules/devstar_devcontainer/k8s_agent/CreateDevcontainer.go
戴明辰 c1ea93e233 !10 [Feature][Fix][Doc] Tencent NAT Auto-Portforwarding
* [Fix] Relocate User Permanent SSH Public Key queries to DevcontainerService Layer
* [Fix] Add Unix Timestamps in DB table `devstar_devcontainer`
* [Feature] Tencent NAT port forwarding
* [Doc] k8s Operator RBAC: ServiceAccount, ClusterRole, ClusterRoleBinding, etc.
* [fix] k8s Operator Reconciler error while converting YAML to JSON
* [Doc] Added DevStar API Doc
* [fix] detailed errors while listing user devcontainers
* [fix] Invalid metadata.labels: value must be no more than 63 characters
2024-10-23 03:05:44 +00:00

150 lines
6.2 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 k8s_agent
import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/devstar_cloud_provider"
"code.gitea.io/gitea/services/devstar_devcontainer/errors"
"context"
"encoding/json"
"fmt"
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_k8s_agent_modules_errors "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/errors"
apimachinery_apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apimachinery_apis_v1_unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
apimachinery_watch "k8s.io/apimachinery/pkg/watch"
dynamic_client "k8s.io/client-go/dynamic"
)
// CreateDevcontainer 创建开发容器
func CreateDevcontainer(ctx *context.Context, client dynamic_client.Interface, opts *devcontainer_dto.CreateDevcontainerOptions) (*devcontainer_api_v1.DevcontainerApp, error) {
if ctx == nil || opts == nil {
return nil, devcontainer_k8s_agent_modules_errors.ErrIllegalDevcontainerParameters{
FieldList: []string{"ctx", "opts"},
Message: "cannot be nil",
}
}
devcontainerApp := &devcontainer_api_v1.DevcontainerApp{
TypeMeta: apimachinery_apis_v1.TypeMeta{
Kind: "DevcontainerApp",
APIVersion: "devcontainer.devstar.cn/v1",
},
ObjectMeta: apimachinery_apis_v1.ObjectMeta{
Name: opts.Name,
Namespace: opts.Namespace,
},
Spec: devcontainer_api_v1.DevcontainerAppSpec{
StatefulSet: devcontainer_api_v1.StatefulSetSpec{
Image: opts.Image,
Command: opts.CommandList,
ContainerPort: opts.ContainerPort,
SSHPublicKeyList: opts.SSHPublicKeyList,
GitRepositoryURL: opts.GitRepositoryURL,
},
},
}
jsonData, err := json.Marshal(devcontainerApp)
if err != nil {
return nil, devcontainer_k8s_agent_modules_errors.ErrOperateDevcontainer{
Action: "Marshal JSON",
Message: err.Error(),
}
}
unstructuredObj := &apimachinery_apis_v1_unstructured.Unstructured{}
_, _, err = apimachinery_apis_v1_unstructured.UnstructuredJSONScheme.Decode(jsonData, &groupVersionKind, unstructuredObj)
if err != nil {
return nil, devcontainer_k8s_agent_modules_errors.ErrOperateDevcontainer{
Action: "build unstructured obj",
Message: err.Error(),
}
}
// 创建 DevContainer Status.nodePortAssigned 信息
_, err = client.Resource(groupVersionResource).Namespace(opts.Namespace).Create(*ctx, unstructuredObj, opts.CreateOptions)
if err != nil {
return nil, devcontainer_k8s_agent_modules_errors.ErrOperateDevcontainer{
Action: "create DevContainer via Dynamic Client",
Message: err.Error(),
}
}
// 注册 watcher 监听 DevContainer Status.nodePortAssigned 信息
watcherTimeoutSeconds := int64(3)
watcher, err := client.Resource(groupVersionResource).Namespace(opts.Namespace).Watch(*ctx, apimachinery_apis_v1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s", opts.Name),
//Watch: false,
TimeoutSeconds: &watcherTimeoutSeconds,
Limit: 1,
})
if err != nil {
return nil, devcontainer_k8s_agent_modules_errors.ErrOperateDevcontainer{
Action: "register watcher of DevContainer NodePort",
Message: err.Error(),
}
}
defer watcher.Stop()
//log.Info(" ===== 开始监听 DevContainer NodePort 分配信息 =====")
var nodePortAssigned int64
for event := range watcher.ResultChan() {
switch event.Type {
case apimachinery_watch.Modified:
if devcontainerUnstructured, ok := event.Object.(*apimachinery_apis_v1_unstructured.Unstructured); ok {
statusDevcontainer, ok, err := apimachinery_apis_v1_unstructured.NestedMap(devcontainerUnstructured.Object, "status")
if err == nil && ok {
nodePortAssigned = statusDevcontainer["nodePortAssigned"].(int64)
if 30000 <= nodePortAssigned && nodePortAssigned <= 32767 {
devcontainerApp.Status.NodePortAssigned = uint16(nodePortAssigned)
//log.Info("DevContainer NodePort Status 更新完成,最新 NodePort = %v", nodePortAssigned)
//break
// 收到 NodePort Service MODIFIED 消息后,更新 NodePort直接返回不再处理后续 Event (否则必须超时3秒才得到 NodePort)
natRuleDescription := "DevContainer: " + devcontainerApp.Name
privatePort := uint64(nodePortAssigned)
publicPort := privatePort
err = devstar_cloud_provider.CreateNATRulePort(privatePort, publicPort, natRuleDescription)
return devcontainerApp, err
}
}
}
}
}
//log.Info(" ===== 结束监听 DevContainer NodePort 分配信息 =====")
/*
// 目前需要更新的字段只有 devcontainerInCluster.Status.NodePortAssigned
// 如果后续有其他较多的需要更新的字段,可以考虑重新查询,目前,暂时只考虑 直接更新内存缓存对象,然后返回即可
// 避免对 k8s API Server 造成访问压力
//response, err := client.Resource(groupVersionResource).Namespace(opts.Namespace).Get(*ctx, opts.Name, apimachinery_apis_v1.GetOptions{})
//jsonData, err = response.MarshalJSON()
//devcontainerInCluster := &devcontainer_api_v1.DevcontainerApp{}
//err = json.Unmarshal(jsonData, devcontainerInCluster)
//if err != nil {
// return nil, devcontainer_errors.ErrOperateDevcontainer{
// Action: "parse response result of in-cluster DevContainer",
// Message: err.Error(),
// }
//}
*/
// 如果执行到这里,说明 k8s 集群中 DevcontainerApp 初始化失败,比如执行下列命令查看出错原因如下:
// $ kubectl get pod -n devstar-studio-ns test-mockrepo1-6c5369588f8911e-0
// NAME READY STATUS RESTARTS AGE
// test-mockrepo1-6c5369588f8911e-0 0/1 Init:CrashLoopBackOff 1 (5s ago) 7s
// 需要删除刚刚创建的 k8s CRD然后返回 DevContainer 初始化失败
optsDeleteInitFailed := &devcontainer_dto.DeleteDevcontainerOptions{
Namespace: setting.Devstar.Devcontainer.Namespace,
Name: devcontainerApp.Name,
}
_ = DeleteDevcontainer(ctx, client, optsDeleteInitFailed)
return nil, errors.ErrOperateDevcontainer{
Action: "Initialize DevContainer",
Message: fmt.Sprintf("DevContainer %v failed to initialize and is thus purged.", devcontainerApp.Name),
}
}