refactor: full-stack restructure with multi-tenancy, workspace management, and K8s diagnostics
- Add Workspace domain (entity, repository, service, handler, DTO) - Add multi-tenant K8s client with tenant binding and quota management - Add K8s diagnostics client (instance diagnostics) - Add authorization middleware (authz package) - Restructure frontend to feature-based architecture (features/) - Add User Management page in configuration - Add AccessDenied page and route guards - Refactor shared components (form inputs, layout, UI) - Update Tailwind config for new design system - Add comprehensive documentation (docs/, tasks/, plans) - Improve cluster service with better kubeconfig handling - Add tests for crypto, config, helm client, tenant binding
This commit is contained in:
@ -27,6 +27,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@ -35,6 +36,7 @@ import (
|
||||
"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/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"
|
||||
@ -72,6 +74,7 @@ func main() {
|
||||
// ===== 5. 创建 Domain Services =====
|
||||
authService := service.NewAuthService(
|
||||
repos.UserRepo,
|
||||
repos.WorkspaceRepo,
|
||||
passwordHasher,
|
||||
tokenGenerator,
|
||||
)
|
||||
@ -97,20 +100,31 @@ func main() {
|
||||
repos.HelmClient,
|
||||
repos.OCIClient,
|
||||
repos.EntryClient,
|
||||
repos.BindingRepo,
|
||||
)
|
||||
instanceService.SetDiagnosticsClient(repos.DiagnosticsClient)
|
||||
instanceService.SetTenantProvisioning(repos.WorkspaceRepo, repos.TenantKubeClient)
|
||||
|
||||
monitoringService := service.NewMonitoringService(
|
||||
repos.ClusterRepo,
|
||||
repos.MetricsClient,
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@ -126,6 +140,7 @@ func main() {
|
||||
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")
|
||||
@ -133,11 +148,13 @@ func main() {
|
||||
// ===== 8. 设置路由 =====
|
||||
router := setupRouter(
|
||||
authHandler,
|
||||
authService,
|
||||
clusterHandler,
|
||||
registryHandler,
|
||||
artifactHandler,
|
||||
instanceHandler,
|
||||
monitoringHandler,
|
||||
workspaceHandler,
|
||||
swaggerHandler,
|
||||
)
|
||||
|
||||
@ -191,11 +208,13 @@ func getEnv(key, defaultValue string) string {
|
||||
// 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)
|
||||
@ -222,45 +241,63 @@ func setupRouter(
|
||||
api := router.PathPrefix("/api/v1").Subrouter()
|
||||
|
||||
// ===== 认证路由 =====
|
||||
api.HandleFunc("/auth/register", authHandler.Register)
|
||||
api.HandleFunc("/auth/login", authHandler.Login)
|
||||
api.HandleFunc("/auth/refresh", authHandler.RefreshToken)
|
||||
|
||||
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)
|
||||
|
||||
// ===== 集群路由 =====
|
||||
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)
|
||||
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)
|
||||
|
||||
// ===== 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)
|
||||
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 路由 =====
|
||||
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)
|
||||
protected.HandleFunc("/registries/{registry_id}/repositories", artifactHandler.ListRepositories).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:.+}/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 路由 =====
|
||||
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)
|
||||
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)
|
||||
|
||||
// ===== 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)
|
||||
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}/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) {
|
||||
@ -275,6 +312,35 @@ func setupRouter(
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user