// @title OCDP Backend API // @version 1.0 // @description OCDP (Open Cloud Development Platform) Backend API // @description // @description RESTful API for managing Kubernetes clusters, OCI registries, and Helm deployments. // // @contact.name API Support // @contact.email support@ocdp.io // // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // // @host localhost:8080 // @BasePath /api/v1 // // @schemes http https // // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization // @description Type "Bearer" followed by a space and JWT token. package main import ( "context" "fmt" "log" "net/http" "os" "strings" "time" "github.com/gorilla/mux" "github.com/ocdp/cluster-service/internal/adapter/input/http/rest" "github.com/ocdp/cluster-service/internal/adapter/output" "github.com/ocdp/cluster-service/internal/adapter/output/k8s" "github.com/ocdp/cluster-service/internal/bootstrap" "github.com/ocdp/cluster-service/internal/domain/service" "github.com/ocdp/cluster-service/internal/pkg/authz" "github.com/ocdp/cluster-service/internal/pkg/crypto" "github.com/ocdp/cluster-service/internal/pkg/jwt" "github.com/ocdp/cluster-service/internal/pkg/password" ) func main() { log.Println("🚀 Starting OCDP Backend (Hexagonal Architecture)") // ===== 1. 读取配置 ===== config := loadConfig() log.Printf("📝 Configuration: mode=%s, port=%s", config.AdapterMode, config.Port) // ===== 2. 创建加密器(用于敏感数据加密存储) ===== encryptor := crypto.NewAESEncryptor(config.EncryptionKey) log.Println("✅ Encryption enabled for sensitive data") // ===== 3. 创建 Output Adapters(通过工厂) ===== factory := output.NewAdapterFactory( output.AdapterMode(config.AdapterMode), encryptor, config.DatabaseURL, ) repos, err := factory.CreateAllRepositories() if err != nil { log.Fatalf("❌ Failed to create adapters: %v", err) } log.Printf("✅ Output Adapters initialized (mode: %s)", config.AdapterMode) // ===== 4. 创建工具包实例 ===== passwordHasher := password.NewHasher() tokenGenerator := jwt.NewJWTManager(config.JWTSecret) log.Println("✅ Utilities initialized") // ===== 5. 创建 Domain Services ===== authService := service.NewAuthService( repos.UserRepo, repos.WorkspaceRepo, passwordHasher, tokenGenerator, ) authService.SetUserLifecycleCleanup( repos.InstanceRepo, repos.ClusterRepo, repos.BindingRepo, repos.TenantKubeClient, ) clusterService := service.NewClusterService( repos.ClusterRepo, ) registryService := service.NewRegistryService( repos.RegistryRepo, repos.OCIClient, ) artifactService := service.NewArtifactService( repos.RegistryRepo, repos.OCIClient, ) instanceService := service.NewInstanceService( repos.InstanceRepo, repos.ClusterRepo, repos.RegistryRepo, repos.HelmClient, repos.OCIClient, repos.EntryClient, repos.BindingRepo, ) instanceService.SetDiagnosticsClient(repos.DiagnosticsClient) instanceService.SetTenantProvisioning(repos.WorkspaceRepo, repos.TenantKubeClient) instanceService.SetScaleClient(k8s.NewScaleClient()) instanceService.SetUserRepository(repos.UserRepo) monitoringService := service.NewMonitoringService( repos.ClusterRepo, repos.MetricsClient, repos.InstanceRepo, repos.UserRepo, ) workspaceService := service.NewWorkspaceService( repos.WorkspaceRepo, repos.BindingRepo, repos.ClusterRepo, repos.TenantKubeClient, repos.AuditRepo, ) log.Println("✅ Domain Services initialized") // ===== 6. 加载并执行 Bootstrap 预注入 ===== bootstrapConfig, err := bootstrap.LoadBootstrapConfig() if err != nil { log.Printf("⚠️ Warning: Failed to load bootstrap config: %v", err) // 使用安全的空配置,避免在配置错误时写入任何预置账号或集群凭据。 bootstrapConfig = bootstrap.GetDefaultBootstrapConfig() } seeder := bootstrap.NewSeeder(repos, passwordHasher, bootstrapConfig) if err := seeder.SeedAll(context.Background()); err != nil { log.Printf("⚠️ Warning: Failed to seed data: %v", err) } // ===== 7. 创建 Input Adapters (REST Handlers) ===== authHandler := rest.NewAuthHandler(authService) clusterHandler := rest.NewClusterHandler(clusterService) registryHandler := rest.NewRegistryHandler(registryService) artifactHandler := rest.NewArtifactHandler(artifactService) instanceHandler := rest.NewInstanceHandler(instanceService) monitoringHandler := rest.NewMonitoringHandler(monitoringService) workspaceHandler := rest.NewWorkspaceHandler(workspaceService) swaggerHandler := rest.NewSwaggerHandler() log.Println("✅ Input Adapters (REST handlers) initialized") // ===== 8. 设置路由 ===== router := setupRouter( authHandler, authService, clusterHandler, registryHandler, artifactHandler, instanceHandler, monitoringHandler, workspaceHandler, swaggerHandler, ) // ===== 9. 启动服务器 ===== addr := fmt.Sprintf(":%s", config.Port) log.Printf("🌐 Server starting on %s", addr) log.Println("") log.Println("📍 Available endpoints:") log.Printf(" - REST API: http://localhost:%s/api/v1", config.Port) log.Printf(" - Swagger UI: http://localhost:%s/api/docs", config.Port) log.Printf(" - OpenAPI Spec: http://localhost:%s/api/docs/openapi.yaml", config.Port) log.Printf(" - Health: http://localhost:%s/health", config.Port) log.Println("") log.Println("✨ Press Ctrl+C to stop") log.Println("") if err := http.ListenAndServe(addr, router); err != nil { log.Fatalf("❌ Server failed: %v", err) } } // Config 应用配置 type Config struct { AdapterMode string Port string JWTSecret string EncryptionKey string DatabaseURL string } // loadConfig 加载配置 func loadConfig() *Config { return &Config{ AdapterMode: getEnv("ADAPTER_MODE", ""), // 默认为空字符串(真实模式) Port: getEnv("PORT", "8080"), JWTSecret: getEnv("JWT_SECRET", "your-secret-key-change-in-production"), EncryptionKey: getEnv("ENCRYPTION_KEY", "default-encryption-key-change-in-production"), DatabaseURL: getEnv("DATABASE_URL", ""), } } // getEnv 获取环境变量,如果不存在则返回默认值 func getEnv(key, defaultValue string) string { value := os.Getenv(key) if value == "" { return defaultValue } return value } // setupRouter 设置路由 func setupRouter( authHandler *rest.AuthHandler, authService *service.AuthService, clusterHandler *rest.ClusterHandler, registryHandler *rest.RegistryHandler, artifactHandler *rest.ArtifactHandler, instanceHandler *rest.InstanceHandler, monitoringHandler *rest.MonitoringHandler, workspaceHandler *rest.WorkspaceHandler, swaggerHandler *rest.SwaggerHandler, ) *mux.Router { router := mux.NewRouter().StrictSlash(true) // 全局中间件 router.Use(loggingMiddleware) router.Use(corsMiddleware) // 健康检查 router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"healthy"}`)) }) // ===== Swagger UI ===== router.HandleFunc("/api/docs", swaggerHandler.ServeSwaggerUI).Methods(http.MethodGet) router.HandleFunc("/api/docs/assets/swagger-ui.css", swaggerHandler.ServeSwaggerCSS).Methods(http.MethodGet) router.HandleFunc("/api/docs/assets/swagger-ui-bundle.js", swaggerHandler.ServeSwaggerBundle).Methods(http.MethodGet) router.HandleFunc("/api/docs/assets/swagger-ui-standalone-preset.js", swaggerHandler.ServeSwaggerStandalonePreset).Methods(http.MethodGet) router.HandleFunc("/api/docs/openapi.yaml", swaggerHandler.ServeOpenAPISpec).Methods(http.MethodGet) // API v1 api := router.PathPrefix("/api/v1").Subrouter() // ===== 认证路由 ===== api.HandleFunc("/auth/login", authHandler.Login).Methods(http.MethodPost) api.HandleFunc("/auth/refresh", authHandler.RefreshToken).Methods(http.MethodPost) api.HandleFunc("/auth/status", authHandler.AuthStatus).Methods(http.MethodGet) api.HandleFunc("/auth/setup", authHandler.Setup).Methods(http.MethodPost) protected := api.PathPrefix("").Subrouter() protected.Use(authMiddleware(authService)) protected.HandleFunc("/auth/me", authHandler.Me).Methods(http.MethodGet) protected.HandleFunc("/auth/register", authHandler.Register).Methods(http.MethodPost) protected.HandleFunc("/users", authHandler.ListUsers).Methods(http.MethodGet) protected.HandleFunc("/users", authHandler.Register).Methods(http.MethodPost) protected.HandleFunc("/users/{user_id}", authHandler.UpdateUser).Methods(http.MethodPut) protected.HandleFunc("/users/{user_id}", authHandler.DeleteUser).Methods(http.MethodDelete) // ===== 集群路由 ===== protected.HandleFunc("/clusters", clusterHandler.CreateCluster).Methods(http.MethodPost) protected.HandleFunc("/clusters", clusterHandler.GetAllClusters).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}", clusterHandler.GetCluster).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}", clusterHandler.UpdateCluster).Methods(http.MethodPut) protected.HandleFunc("/clusters/{cluster_id}", clusterHandler.DeleteCluster).Methods(http.MethodDelete) protected.HandleFunc("/clusters/{cluster_id}/health", clusterHandler.GetClusterHealth).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}/stats", monitoringHandler.GetClusterStats).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}/kubeconfig", workspaceHandler.IssueClusterKubeconfig).Methods(http.MethodGet) // ===== Registry 路由 ===== protected.HandleFunc("/registries", registryHandler.CreateRegistry).Methods(http.MethodPost) protected.HandleFunc("/registries", registryHandler.GetAllRegistries).Methods(http.MethodGet) protected.HandleFunc("/registries/{registry_id}", registryHandler.GetRegistry).Methods(http.MethodGet) protected.HandleFunc("/registries/{registry_id}", registryHandler.UpdateRegistry).Methods(http.MethodPut) protected.HandleFunc("/registries/{registry_id}", registryHandler.DeleteRegistry).Methods(http.MethodDelete) protected.HandleFunc("/registries/{registry_id}/health", registryHandler.GetRegistryHealth).Methods(http.MethodGet) // ===== Artifact 路由 ===== protected.HandleFunc("/registries/{registry_id}/repositories", artifactHandler.ListRepositories).Methods(http.MethodGet) protected.HandleFunc("/repositories/{repository_name:.+}/tags", artifactHandler.ListRepositoryTags).Methods(http.MethodGet) protected.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts", artifactHandler.ListArtifacts).Methods(http.MethodGet) protected.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/tags", artifactHandler.ListRepositoryTags).Methods(http.MethodGet) protected.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}", artifactHandler.GetArtifact).Methods(http.MethodGet) protected.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}/values-schema", artifactHandler.GetArtifactValuesSchema).Methods(http.MethodGet) protected.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}/values-yaml", artifactHandler.GetArtifactValuesYAML).Methods(http.MethodGet) // ===== Instance 路由 ===== protected.HandleFunc("/clusters/{cluster_id}/instances", instanceHandler.CreateInstance).Methods(http.MethodPost) protected.HandleFunc("/clusters/{cluster_id}/instances", instanceHandler.ListInstances).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.GetInstance).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.UpdateInstance).Methods(http.MethodPut) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.DeleteInstance).Methods(http.MethodDelete) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}/entries", instanceHandler.ListInstanceEntries).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}/diagnostics", instanceHandler.GetInstanceDiagnostics).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}/logs/stream", instanceHandler.StreamInstanceLogs).Methods(http.MethodGet) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}/scale", instanceHandler.ScaleInstance).Methods(http.MethodPost) protected.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}/values-diff", instanceHandler.GetInstanceValuesDiff).Methods(http.MethodGet) // ===== Monitoring 路由 ===== protected.HandleFunc("/monitoring/clusters", monitoringHandler.ListClusterMonitoring).Methods(http.MethodGet) protected.HandleFunc("/monitoring/clusters/{cluster_id}", monitoringHandler.GetClusterMonitoring).Methods(http.MethodGet) protected.HandleFunc("/monitoring/clusters/{cluster_id}/metrics", monitoringHandler.GetClusterMonitoring).Methods(http.MethodGet) protected.HandleFunc("/monitoring/clusters/{cluster_id}/nodes", monitoringHandler.GetNodeMetrics).Methods(http.MethodGet) protected.HandleFunc("/monitoring/summary", monitoringHandler.GetMonitoringSummary).Methods(http.MethodGet) // ===== Workspace 路由 ===== protected.HandleFunc("/workspaces", workspaceHandler.ListWorkspaces).Methods(http.MethodGet) protected.HandleFunc("/workspaces", workspaceHandler.CreateWorkspace).Methods(http.MethodPost) protected.HandleFunc("/workspaces/credentials/kubeconfig", workspaceHandler.IssueCurrentKubeconfig).Methods(http.MethodGet) protected.HandleFunc("/workspaces/{workspace_id}/clusters", workspaceHandler.InitClusterBinding).Methods(http.MethodPost) protected.HandleFunc("/workspaces/{workspace_id}/kubeconfig", workspaceHandler.IssueKubeconfig).Methods(http.MethodPost) protected.HandleFunc("/workspaces/{workspace_id}/suspend", workspaceHandler.SuspendWorkspace).Methods(http.MethodPost) // 处理 MethodNotAllowed 错误(OPTIONS 请求会触发) router.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { // CORS 预检已在中间件处理,这里直接返回 w.WriteHeader(http.StatusNoContent) return } http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) }) return router } func authMiddleware(authService *service.AuthService) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { header := r.Header.Get("Authorization") if !strings.HasPrefix(header, "Bearer ") { writeJSONError(w, http.StatusUnauthorized, "Unauthorized", "missing bearer token") return } token := strings.TrimSpace(strings.TrimPrefix(header, "Bearer ")) if token == "" { writeJSONError(w, http.StatusUnauthorized, "Unauthorized", "missing bearer token") return } principal, err := authService.VerifyAccessToken(r.Context(), token) if err != nil { writeJSONError(w, http.StatusUnauthorized, "Unauthorized", err.Error()) return } next.ServeHTTP(w, r.WithContext(authz.WithPrincipal(r.Context(), principal))) }) } } func writeJSONError(w http.ResponseWriter, status int, code, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _, _ = w.Write([]byte(fmt.Sprintf(`{"error":%q,"message":%q}`, code, message))) } // loggingMiddleware 日志中间件 func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) log.Printf("%s %s %s %v", r.Method, r.RequestURI, r.RemoteAddr, time.Since(start)) }) } // corsMiddleware CORS 中间件 func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") if origin != "" { w.Header().Add("Vary", "Origin") if corsOriginAllowed(origin) { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Credentials", "true") } } w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") w.Header().Set("Access-Control-Max-Age", "86400") // 处理 OPTIONS 预检请求 if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } func corsOriginAllowed(origin string) bool { origin = strings.TrimSpace(origin) if origin == "" { return false } for _, allowed := range corsAllowedOrigins() { if origin == allowed { return true } } return false } func corsAllowedOrigins() []string { configured := strings.TrimSpace(os.Getenv("CORS_ALLOWED_ORIGINS")) if configured == "" { configured = strings.TrimSpace(os.Getenv("ALLOWED_ORIGINS")) } if configured == "" { return []string{ "http://localhost:3000", "http://localhost:5173", "http://localhost:8080", "http://localhost:18080", "http://localhost:18081", "http://127.0.0.1:3000", "http://127.0.0.1:5173", "http://127.0.0.1:8080", "http://127.0.0.1:18080", "http://127.0.0.1:18081", "http://10.6.80.114:18080", } } origins := make([]string, 0) for _, origin := range strings.Split(configured, ",") { origin = strings.TrimSpace(origin) if origin == "" || origin == "*" { continue } origins = append(origins, origin) } return origins }