// cmd/server/main.go — точка входа fission-console сервера. // Отвечает только за: парсинг env vars, создание зависимостей, запуск HTTP сервера. // Вся бизнес-логика находится в internal/api/, internal/fission/, internal/runtime/. package main import ( "log" "net/http" "os" "strings" "time" "fission-console/internal/api" "fission-console/internal/auth" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" ) func main() { kubeconfig := strings.TrimSpace(os.Getenv("KUBECONFIG")) namespace := envDefault("FISSION_NAMESPACE", "default") routerURL := strings.TrimRight(envDefault("FISSION_ROUTER_URL", "http://router.fission.svc.cluster.local"), "/") port := envDefault("PORT", "8090") httpTimeout := envDurationDefault("FISSION_HTTP_TIMEOUT", 30*time.Second) invokeTimeout := envDurationDefault("FISSION_INVOKE_TIMEOUT", 20*time.Second) // Строим kubernetes config: сначала kubeconfig файл, потом in-cluster, потом default cfg, err := buildConfig(kubeconfig) if err != nil { log.Fatalf("build kube config: %v", err) } dyn, err := dynamic.NewForConfig(cfg) if err != nil { log.Fatalf("create dynamic client: %v", err) } testMode := os.Getenv("FISSION_TEST_MODE") == "true" var jwtAuth auth.Authenticator if testMode { jwtAuth = &auth.TestAuthenticator{} } else { jwtAuth = auth.NewDeckAuthenticator(auth.DefaultDeckAPIs, nil) } authenticator := &auth.MultiAuthenticator{ JWT: jwtAuth, Demo: &auth.DemoAuthenticator{}, } srv := api.NewServer(api.Config{ Dyn: dyn, Namespace: namespace, RouterURL: routerURL, HTTPTimeout: httpTimeout, InvokeTimeout: invokeTimeout, SATokenPath: envDefault("SA_TOKEN_PATH", "/var/run/secrets/kubernetes.io/serviceaccount/token"), AuthUser: envDefault("FISSION_AUTH_USERNAME", ""), AuthPass: envDefault("FISSION_AUTH_PASSWORD", ""), TestMode: testMode, Authenticator: authenticator, // --- ai/ask feature --- LLMUrl: envDefault("FISSION_LLM_URL", "https://api.aillm.ru"), LLMKey: os.Getenv("FISSION_LLM_KEY"), // --- end ai/ask feature --- }) // Запускаем фоновые горутины: reaper истёкших функций nsm := srv.NSManager() nsm.StartExpiryReaper(envDurationDefault("REAPER_INTERVAL", 5*time.Minute)) // StartNSReconciler удалён — за FISSION_RESOURCE_NAMESPACES теперь отвечает Layer 1 NSWatcher httpServer := &http.Server{ Addr: ":" + port, Handler: srv.Handler(), // ReadHeaderTimeout защищает от Slowloris атаки (медленная отправка заголовков) ReadHeaderTimeout: 10 * time.Second, } log.Printf("fission-console listening on :%s (namespace=%s)", port, namespace) log.Fatal(httpServer.ListenAndServe()) } // buildConfig строит kubernetes rest.Config в порядке приоритета: // 1. KUBECONFIG env var или флаг — для локальной разработки // 2. In-cluster config — для запуска внутри pod // 3. Default kubeconfig — ~/.kube/config func buildConfig(kubeconfig string) (*rest.Config, error) { if kubeconfig != "" { return clientcmd.BuildConfigFromFlags("", kubeconfig) } cfg, err := rest.InClusterConfig() if err == nil { return cfg, nil } // Fallback: пробуем default kubeconfig (empty rules → ~/.kube/config) loadingRules := &clientcmd.ClientConfigLoadingRules{} clientCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) return clientCfg.ClientConfig() } func envDefault(key, fallback string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v } return fallback } func envDurationDefault(key string, fallback time.Duration) time.Duration { raw := strings.TrimSpace(os.Getenv(key)) if raw == "" { return fallback } d, err := time.ParseDuration(raw) if err != nil || d <= 0 { log.Printf("invalid duration for %s=%q, using default %s", key, raw, fallback) return fallback } return d }