Files
ocdp-go/backend/cmd/api/main.go
Ivan087 47849042a7 feat: complete E2E deployment flow with storage layered config and values template versioning
- Instance deployment: charts browser, deploy modal, instances list
- Values Template version management (create/history/rollback)
- Storage layered config (cluster > workspace > shared priority)
- Cluster credential decryptIfNeeded for mixed encrypted/plaintext kubeconfig
- YAML syntax validation (client-side + server-side warning)
- Frontend: charts, instances, storage, templates, admin pages
- Backend: storage service, instance service, cluster service, helm client
- Multi-Tenant Kubeconfig.md: added by user
2026-04-30 16:31:00 +08:00

500 lines
20 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.

// @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)
})
}
}