package api import ( "context" "net/http" "os" "strings" "time" "fission-console/internal/fission" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) // handleNSStatus возвращает статус инициализации пользовательского namespace. // Используется UI для отображения прогресса при первом входе нового пользователя. // // Три стадии: // 1. Namespace существует и Active // 2. Namespace добавлен в FISSION_RESOURCE_NAMESPACES executor deployment // 3. Executor pod Running + Ready (готов принимать функции) func (s *Server) handleNSStatus(w http.ResponseWriter, r *http.Request) { ns := s.userNS(r) ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) defer cancel() fissionNS := os.Getenv("FISSION_SYSTEM_NAMESPACE") if fissionNS == "" { fissionNS = "fission" } type stageInfo struct { Name string `json:"name"` Done bool `json:"done"` } stages := []stageInfo{ {Name: "Создание пространства имён"}, {Name: "Регистрация в Fission"}, {Name: "Прогрев окружений"}, } // Stage 1: namespace существует и Active nsObj, err := s.dyn.Resource(fission.NamespaceGVR).Get(ctx, ns, metav1.GetOptions{}) if err == nil { phase, _, _ := unstructured.NestedString(nsObj.Object, "status", "phase") stages[0].Done = phase == "Active" } // Stage 2: namespace в FISSION_RESOURCE_NAMESPACES executor deployment if stages[0].Done { execDep, err2 := s.dyn.Resource(fission.DeploymentGVR).Namespace(fissionNS).Get(ctx, "executor", metav1.GetOptions{}) if err2 == nil { stages[1].Done = nsInFissionEnv(execDep, ns) } } // Stage 3: executor pod Running + Ready if stages[1].Done { podGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} podList, err3 := s.dyn.Resource(podGVR).Namespace(fissionNS).List(ctx, metav1.ListOptions{ LabelSelector: "svc=executor", }) if err3 == nil { for _, pod := range podList.Items { phase, _, _ := unstructured.NestedString(pod.Object, "status", "phase") if phase != "Running" { continue } conditions, _, _ := unstructured.NestedSlice(pod.Object, "status", "conditions") for _, c := range conditions { cond, ok := c.(map[string]any) if !ok { continue } if cond["type"] == "Ready" && cond["status"] == "True" { stages[2].Done = true } } } } } ready := stages[0].Done && stages[1].Done && stages[2].Done writeAnyJSON(w, http.StatusOK, map[string]any{ "ready": ready, "stages": stages, }) } // nsInFissionEnv проверяет что namespace ns содержится в FISSION_RESOURCE_NAMESPACES // первого контейнера данного deployment. func nsInFissionEnv(dep *unstructured.Unstructured, ns string) bool { containers, _, _ := unstructured.NestedSlice(dep.Object, "spec", "template", "spec", "containers") for _, c := range containers { cont, ok := c.(map[string]any) if !ok { continue } envs, _, _ := unstructured.NestedSlice(cont, "env") for _, e := range envs { env, ok := e.(map[string]any) if !ok { continue } if env["name"] == "FISSION_RESOURCE_NAMESPACES" { if v, ok := env["value"].(string); ok { for _, p := range strings.Split(v, ",") { if strings.TrimSpace(p) == ns { return true } } } } } break // только первый контейнер } return false }