// @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/bootstrap" "github.com/ocdp/cluster-service/internal/domain/service" "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, passwordHasher, tokenGenerator, ) 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, ) monitoringService := service.NewMonitoringService( repos.ClusterRepo, repos.MetricsClient, ) // Workspace Service workspaceService := service.NewWorkspaceService( repos.WorkspaceRepo, repos.QuotaRepo, repos.UserRepo, ) // User Management Service userManagementService := service.NewUserManagementService( repos.UserRepo, repos.WorkspaceRepo, passwordHasher, ) 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) swaggerHandler := rest.NewSwaggerHandler() // Workspace Handler workspaceHandler := rest.NewWorkspaceHandler(workspaceService, authService) // User Management Handler (Admin only) userManagementHandler := rest.NewUserManagementHandler(userManagementService, authService, workspaceService) // User Handler userHandler := rest.NewUserHandler(authService, workspaceService) // Storage Handler storageService := service.NewStorageService(repos.StorageRepo) storageHandler := rest.NewStorageHandler(storageService) // Wire storage service into instance service for layered storage config instanceService.SetStorageService(storageService) // Chart Reference Handler chartRefService := service.NewChartReferenceService(repos.ChartRefRepo, repos.RegistryRepo) chartRefHandler := rest.NewChartReferenceHandler(chartRefService) // Values Template Handler valuesTemplateService := service.NewValuesTemplateService(repos.ValuesTemplateRepo, repos.ChartRefRepo) valuesTemplateHandler := rest.NewValuesTemplateHandler(valuesTemplateService) log.Println("✅ Input Adapters (REST handlers) initialized") // ===== 8. 设置路由 ===== router := setupRouter( authHandler, clusterHandler, registryHandler, artifactHandler, instanceHandler, monitoringHandler, swaggerHandler, workspaceHandler, userManagementHandler, userHandler, storageHandler, chartRefHandler, valuesTemplateHandler, tokenGenerator, config.AllowedOrigins, ) // ===== 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 AllowedOrigins []string } // loadConfig 加载配置 func loadConfig() *Config { allowedOrigins := getEnv("ALLOWED_DEV_ORIGINS", "") var origins []string if allowedOrigins != "" { origins = strings.Split(allowedOrigins, ",") } 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", ""), AllowedOrigins: origins, } } // getEnv 获取环境变量,如果不存在则返回默认值 func getEnv(key, defaultValue string) string { value := os.Getenv(key) if value == "" { return defaultValue } return value } // setupRouter 设置路由 func setupRouter( authHandler *rest.AuthHandler, clusterHandler *rest.ClusterHandler, registryHandler *rest.RegistryHandler, artifactHandler *rest.ArtifactHandler, instanceHandler *rest.InstanceHandler, monitoringHandler *rest.MonitoringHandler, swaggerHandler *rest.SwaggerHandler, workspaceHandler *rest.WorkspaceHandler, userManagementHandler *rest.UserManagementHandler, userHandler *rest.UserHandler, storageHandler *rest.StorageHandler, chartRefHandler *rest.ChartReferenceHandler, valuesTemplateHandler *rest.ValuesTemplateHandler, tokenGenerator *jwt.JWTManager, allowedOrigins []string, ) *mux.Router { router := mux.NewRouter().StrictSlash(true) // 全局中间件 router.Use(loggingMiddleware) router.Use(corsMiddleware(allowedOrigins)) // 预检请求处理 - 必须放在路由注册之前 router.HandleFunc("/{path:.*}", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { origin := r.Header.Get("Origin") if origin == "" { origin = "*" } w.Header().Set("Access-Control-Allow-Origin", origin) 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-Allow-Credentials", "true") w.Header().Set("Access-Control-Max-Age", "86400") w.WriteHeader(http.StatusNoContent) return } // 非 OPTIONS 请求返回 404 http.NotFound(w, r) }).Methods(http.MethodOptions) // JWT 解析中间件 - 为所有需要认证的请求设置用户信息 header jwtMiddleware := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 跳过认证路由 if r.URL.Path == "/api/v1/auth/login" || r.URL.Path == "/api/v1/auth/register" || r.URL.Path == "/api/v1/auth/refresh" { next.ServeHTTP(w, r) return } authHeader := r.Header.Get("Authorization") if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { token := strings.TrimPrefix(authHeader, "Bearer ") userID, username, role, workspaceID, err := tokenGenerator.Verify(token) if err == nil && userID != "" { // 设置 header 供 handlers 使用 r.Header.Set("X-User-ID", userID) r.Header.Set("X-Username", username) r.Header.Set("X-User-Role", role) r.Header.Set("X-Workspace-ID", workspaceID) } } next.ServeHTTP(w, r) }) } // 健康检查 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() // 应用 CORS 和 JWT 中间件到所有 API 路由 api.Use(corsMiddleware(allowedOrigins)) api.Use(jwtMiddleware) // ===== 认证路由 ===== api.HandleFunc("/auth/register", authHandler.Register) api.HandleFunc("/auth/login", authHandler.Login) api.HandleFunc("/auth/refresh", authHandler.RefreshToken) // ===== 用户账户路由 ===== api.HandleFunc("/users/me", userHandler.GetCurrentUser).Methods(http.MethodGet) api.HandleFunc("/users/me/password", userHandler.ChangePassword).Methods(http.MethodPut) api.HandleFunc("/users/me/workspace", userHandler.GetCurrentUserWorkspace).Methods(http.MethodGet) // ===== 用户管理路由(Admin) ===== api.HandleFunc("/admin/users", userManagementHandler.CreateUser).Methods(http.MethodPost) api.HandleFunc("/admin/users", userManagementHandler.ListUsers).Methods(http.MethodGet) api.HandleFunc("/admin/users/{user_id}", userManagementHandler.GetUser).Methods(http.MethodGet) api.HandleFunc("/admin/users/{user_id}", userManagementHandler.UpdateUser).Methods(http.MethodPut) api.HandleFunc("/admin/users/{user_id}/active", userManagementHandler.SetUserActive).Methods(http.MethodPut) api.HandleFunc("/admin/users/{user_id}/workspace", userManagementHandler.ChangeUserWorkspace).Methods(http.MethodPut) api.HandleFunc("/admin/users/{user_id}/password", userManagementHandler.ResetPassword).Methods(http.MethodPut) api.HandleFunc("/admin/users/{user_id}", userManagementHandler.DeleteUser).Methods(http.MethodDelete) // ===== Workspace 路由 ===== api.HandleFunc("/workspaces", workspaceHandler.CreateWorkspace).Methods(http.MethodPost) api.HandleFunc("/workspaces", workspaceHandler.ListWorkspaces).Methods(http.MethodGet) api.HandleFunc("/workspaces/{workspace_id}", workspaceHandler.GetWorkspace).Methods(http.MethodGet) api.HandleFunc("/workspaces/{workspace_id}", workspaceHandler.UpdateWorkspace).Methods(http.MethodPut) api.HandleFunc("/workspaces/{workspace_id}", workspaceHandler.DeleteWorkspace).Methods(http.MethodDelete) api.HandleFunc("/workspaces/{workspace_id}/quotas", workspaceHandler.GetWorkspaceQuotas).Methods(http.MethodGet) api.HandleFunc("/workspaces/{workspace_id}/quotas", workspaceHandler.SetWorkspaceQuotas).Methods(http.MethodPut) // ===== 集群路由 ===== api.HandleFunc("/clusters", clusterHandler.CreateCluster).Methods(http.MethodPost) api.HandleFunc("/clusters", clusterHandler.GetAllClusters).Methods(http.MethodGet) api.HandleFunc("/clusters/{cluster_id}", clusterHandler.GetCluster).Methods(http.MethodGet) api.HandleFunc("/clusters/{cluster_id}", clusterHandler.UpdateCluster).Methods(http.MethodPut) api.HandleFunc("/clusters/{cluster_id}", clusterHandler.DeleteCluster).Methods(http.MethodDelete) api.HandleFunc("/clusters/{cluster_id}/health", clusterHandler.GetClusterHealth).Methods(http.MethodGet) // ===== Registry 路由 ===== api.HandleFunc("/registries", registryHandler.CreateRegistry).Methods(http.MethodPost) api.HandleFunc("/registries", registryHandler.GetAllRegistries).Methods(http.MethodGet) api.HandleFunc("/registries/{registry_id}", registryHandler.GetRegistry).Methods(http.MethodGet) api.HandleFunc("/registries/{registry_id}", registryHandler.UpdateRegistry).Methods(http.MethodPut) api.HandleFunc("/registries/{registry_id}", registryHandler.DeleteRegistry).Methods(http.MethodDelete) api.HandleFunc("/registries/{registry_id}/health", registryHandler.GetRegistryHealth).Methods(http.MethodGet) // ===== Storage Backend 路由 ===== api.HandleFunc("/storage-backends", storageHandler.CreateStorage).Methods(http.MethodPost) api.HandleFunc("/storage-backends", storageHandler.GetAllStorage).Methods(http.MethodGet) api.HandleFunc("/storage-backends/resolve", storageHandler.ResolveStorage).Methods(http.MethodGet) api.HandleFunc("/storage-backends/{storage_id}", storageHandler.GetStorage).Methods(http.MethodGet) api.HandleFunc("/storage-backends/{storage_id}", storageHandler.UpdateStorage).Methods(http.MethodPut) api.HandleFunc("/storage-backends/{storage_id}", storageHandler.DeleteStorage).Methods(http.MethodDelete) // ===== Chart Reference 路由 ===== api.HandleFunc("/chart-references", chartRefHandler.CreateChartReference).Methods(http.MethodPost) api.HandleFunc("/chart-references", chartRefHandler.GetAllChartReferences).Methods(http.MethodGet) api.HandleFunc("/chart-references/{chart_reference_id}", chartRefHandler.GetChartReference).Methods(http.MethodGet) api.HandleFunc("/chart-references/{chart_reference_id}", chartRefHandler.UpdateChartReference).Methods(http.MethodPut) api.HandleFunc("/chart-references/{chart_reference_id}", chartRefHandler.DeleteChartReference).Methods(http.MethodDelete) // ===== Values Template 路由 ===== api.HandleFunc("/values-templates", valuesTemplateHandler.CreateValuesTemplate).Methods(http.MethodPost) api.HandleFunc("/values-templates", valuesTemplateHandler.GetAllValuesTemplates).Methods(http.MethodGet) api.HandleFunc("/values-templates/{template_id}", valuesTemplateHandler.GetValuesTemplate).Methods(http.MethodGet) api.HandleFunc("/values-templates/{template_id}", valuesTemplateHandler.UpdateValuesTemplate).Methods(http.MethodPut) api.HandleFunc("/values-templates/{template_id}", valuesTemplateHandler.DeleteValuesTemplate).Methods(http.MethodDelete) api.HandleFunc("/chart-references/{chart_reference_id}/values-templates", valuesTemplateHandler.GetValuesTemplatesByChartReference).Methods(http.MethodGet) api.HandleFunc("/chart-references/{chart_reference_id}/values-templates/history", valuesTemplateHandler.GetValuesTemplateHistory).Methods(http.MethodGet) api.HandleFunc("/chart-references/{chart_reference_id}/values-templates/rollback", valuesTemplateHandler.RollbackValuesTemplate).Methods(http.MethodPost) // ===== Artifact 路由 ===== api.HandleFunc("/registries/{registry_id}/repositories", artifactHandler.ListRepositories).Methods(http.MethodGet) api.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts", artifactHandler.ListArtifacts).Methods(http.MethodGet) api.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}", artifactHandler.GetArtifact).Methods(http.MethodGet) api.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}/values-schema", artifactHandler.GetArtifactValuesSchema).Methods(http.MethodGet) api.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}/values", artifactHandler.GetArtifactValues).Methods(http.MethodGet) // ===== Instance 路由 ===== api.HandleFunc("/clusters/{cluster_id}/instances", instanceHandler.CreateInstance).Methods(http.MethodPost) api.HandleFunc("/clusters/{cluster_id}/instances", instanceHandler.ListInstances).Methods(http.MethodGet) api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.GetInstance).Methods(http.MethodGet) api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.UpdateInstance).Methods(http.MethodPut) api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.DeleteInstance).Methods(http.MethodDelete) api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}/entries", instanceHandler.ListInstanceEntries).Methods(http.MethodGet) // ===== Monitoring 路由 ===== api.HandleFunc("/monitoring/clusters", monitoringHandler.ListClusterMonitoring).Methods(http.MethodGet) api.HandleFunc("/monitoring/clusters/{cluster_id}", monitoringHandler.GetClusterMonitoring).Methods(http.MethodGet) api.HandleFunc("/monitoring/clusters/{cluster_id}/nodes", monitoringHandler.GetNodeMetrics).Methods(http.MethodGet) api.HandleFunc("/monitoring/summary", monitoringHandler.GetMonitoringSummary).Methods(http.MethodGet) // 处理 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 } // 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(allowedOrigins []string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") // 验证 origin 是否在允许列表中 if origin != "" && len(allowedOrigins) > 0 { allowed := false for _, ao := range allowedOrigins { if ao == origin || ao == "*" { allowed = true break } } if !allowed { // Origin 不在允许列表中,拒绝请求 w.Header().Set("Access-Control-Allow-Origin", "") w.WriteHeader(http.StatusForbidden) return } } // 如果没有配置 allowedOrigins,默认允许所有 if len(allowedOrigins) == 0 { if origin == "" { origin = "*" } } // 优先处理 OPTIONS 预检请求 if r.Method == http.MethodOptions { w.Header().Set("Access-Control-Allow-Origin", origin) 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-Allow-Credentials", "true") w.Header().Set("Access-Control-Max-Age", "86400") w.WriteHeader(http.StatusNoContent) return } // 设置 CORS 头 w.Header().Set("Access-Control-Allow-Origin", origin) 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-Allow-Credentials", "true") w.Header().Set("Access-Control-Max-Age", "86400") next.ServeHTTP(w, r) }) } }