- 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
333 lines
9.3 KiB
Go
333 lines
9.3 KiB
Go
package rest
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
|
||
"github.com/gorilla/mux"
|
||
"github.com/ocdp/cluster-service/internal/adapter/input/http/dto"
|
||
"github.com/ocdp/cluster-service/internal/domain/entity"
|
||
"github.com/ocdp/cluster-service/internal/domain/service"
|
||
)
|
||
|
||
// WorkspaceHandler 工作空间 HTTP 处理程序
|
||
type WorkspaceHandler struct {
|
||
workspaceService *service.WorkspaceService
|
||
authService *service.AuthService
|
||
}
|
||
|
||
// NewWorkspaceHandler 创建工作空间处理程序
|
||
func NewWorkspaceHandler(workspaceService *service.WorkspaceService, authService *service.AuthService) *WorkspaceHandler {
|
||
return &WorkspaceHandler{
|
||
workspaceService: workspaceService,
|
||
authService: authService,
|
||
}
|
||
}
|
||
|
||
// CreateWorkspace 创建工作空间
|
||
// @Summary 创建工作空间
|
||
// @Description 创建新的工作空间(Admin 专用,支持 cluster_ids 和初始配额)
|
||
// @Tags workspace
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body dto.CreateWorkspaceRequest true "创建工作空间请求"
|
||
// @Success 200 {object} dto.WorkspaceDTO
|
||
// @Router /workspaces [post]
|
||
func (h *WorkspaceHandler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
|
||
// 检查权限(Admin)
|
||
if !h.requireAdmin(w, r) {
|
||
return
|
||
}
|
||
|
||
var req dto.CreateWorkspaceRequest
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
respondError(w, http.StatusBadRequest, "Invalid request body", "")
|
||
return
|
||
}
|
||
|
||
// 获取创建者 ID
|
||
userID := GetUserIDFromRequest(r)
|
||
|
||
// 准备配额
|
||
quotas := make(map[entity.ResourceType]struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
})
|
||
if req.CPU != nil {
|
||
quotas[entity.ResourceCPU] = struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
}{req.CPU.HardLimit, req.CPU.SoftLimit}
|
||
}
|
||
if req.GPU != nil {
|
||
quotas[entity.ResourceGPU] = struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
}{req.GPU.HardLimit, req.GPU.SoftLimit}
|
||
}
|
||
if req.GPUMemory != nil {
|
||
quotas[entity.ResourceGPUMemory] = struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
}{req.GPUMemory.HardLimit, req.GPUMemory.SoftLimit}
|
||
}
|
||
|
||
workspace, err := h.workspaceService.Create(r.Context(), req.Name, req.Description, userID, req.ClusterIDs, quotas)
|
||
if err != nil {
|
||
respondError(w, http.StatusBadRequest, err.Error(), "")
|
||
return
|
||
}
|
||
|
||
respondSuccess(w, "", dto.WorkspaceDTOFromEntity(workspace))
|
||
}
|
||
|
||
// GetWorkspace 获取工作空间
|
||
// @Summary 获取工作空间
|
||
// @Description 获取指定工作空间的详细信息和配额
|
||
// @Tags workspace
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param workspace_id path string true "工作空间 ID"
|
||
// @Success 200 {object} dto.WorkspaceResponse
|
||
// @Router /workspaces/{workspace_id} [get]
|
||
func (h *WorkspaceHandler) GetWorkspace(w http.ResponseWriter, r *http.Request) {
|
||
vars := mux.Vars(r)
|
||
workspaceID := vars["workspace_id"]
|
||
|
||
workspace, err := h.workspaceService.GetByID(r.Context(), workspaceID)
|
||
if err != nil {
|
||
respondError(w, http.StatusNotFound, "Workspace not found", "")
|
||
return
|
||
}
|
||
|
||
// 检查访问权限
|
||
if !h.canAccessWorkspace(w, r, workspace.ID) {
|
||
return
|
||
}
|
||
|
||
// 获取配额
|
||
quotas, _ := h.workspaceService.GetQuotas(r.Context(), workspace.ID)
|
||
|
||
response := dto.WorkspaceResponse{
|
||
Workspace: dto.WorkspaceDTOFromEntity(workspace),
|
||
Quotas: dto.QuotaDTOsFromEntities(quotas),
|
||
}
|
||
|
||
respondSuccess(w, "", response)
|
||
}
|
||
|
||
// UpdateWorkspace 更新工作空间
|
||
// @Summary 更新工作空间
|
||
// @Description 更新工作空间信息(Admin 专用)
|
||
// @Tags workspace
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param workspace_id path string true "工作空间 ID"
|
||
// @Param request body dto.UpdateWorkspaceRequest true "更新工作空间请求"
|
||
// @Success 200 {object} dto.WorkspaceDTO
|
||
// @Router /workspaces/{workspace_id} [put]
|
||
func (h *WorkspaceHandler) UpdateWorkspace(w http.ResponseWriter, r *http.Request) {
|
||
// 检查权限(Admin)
|
||
if !h.requireAdmin(w, r) {
|
||
return
|
||
}
|
||
|
||
vars := mux.Vars(r)
|
||
workspaceID := vars["workspace_id"]
|
||
|
||
workspace, err := h.workspaceService.GetByID(r.Context(), workspaceID)
|
||
if err != nil {
|
||
respondError(w, http.StatusNotFound, "Workspace not found", "")
|
||
return
|
||
}
|
||
|
||
var req dto.UpdateWorkspaceRequest
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
respondError(w, http.StatusBadRequest, "Invalid request body", "")
|
||
return
|
||
}
|
||
|
||
if req.Name != "" {
|
||
workspace.Name = req.Name
|
||
}
|
||
if req.Description != "" {
|
||
workspace.Description = req.Description
|
||
}
|
||
if req.ClusterIDs != nil {
|
||
workspace.ClusterIDs = req.ClusterIDs
|
||
}
|
||
|
||
if err := h.workspaceService.Update(r.Context(), workspace); err != nil {
|
||
respondError(w, http.StatusBadRequest, err.Error(), "")
|
||
return
|
||
}
|
||
|
||
respondSuccess(w, "", dto.WorkspaceDTOFromEntity(workspace))
|
||
}
|
||
|
||
// DeleteWorkspace 删除工作空间
|
||
// @Summary 删除工作空间
|
||
// @Description 删除指定工作空间(Admin 专用)
|
||
// @Tags workspace
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param workspace_id path string true "工作空间 ID"
|
||
// @Success 200
|
||
// @Router /workspaces/{workspace_id} [delete]
|
||
func (h *WorkspaceHandler) DeleteWorkspace(w http.ResponseWriter, r *http.Request) {
|
||
// 检查权限(Admin)
|
||
if !h.requireAdmin(w, r) {
|
||
return
|
||
}
|
||
|
||
vars := mux.Vars(r)
|
||
workspaceID := vars["workspace_id"]
|
||
|
||
if err := h.workspaceService.Delete(r.Context(), workspaceID); err != nil {
|
||
respondError(w, http.StatusBadRequest, err.Error(), "")
|
||
return
|
||
}
|
||
|
||
respondSuccess(w, "", nil)
|
||
}
|
||
|
||
// ListWorkspaces 列出所有工作空间
|
||
// @Summary 列出所有工作空间
|
||
// @Description 获取所有工作空间列表(Admin 专用)
|
||
// @Tags workspace
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Success 200 {object} dto.WorkspaceListResponse
|
||
// @Router /workspaces [get]
|
||
func (h *WorkspaceHandler) ListWorkspaces(w http.ResponseWriter, r *http.Request) {
|
||
// 检查权限(Admin)
|
||
if !h.requireAdmin(w, r) {
|
||
return
|
||
}
|
||
|
||
workspaces, err := h.workspaceService.List(r.Context())
|
||
if err != nil {
|
||
respondError(w, http.StatusInternalServerError, err.Error(), "")
|
||
return
|
||
}
|
||
|
||
respondSuccess(w, "", dto.WorkspaceListResponse{
|
||
Workspaces: dto.WorkspaceDTOsFromEntities(workspaces),
|
||
Total: len(workspaces),
|
||
})
|
||
}
|
||
|
||
// GetWorkspaceQuotas 获取工作空间配额
|
||
// @Summary 获取工作空间配额
|
||
// @Description 获取指定工作空间的资源配额
|
||
// @Tags workspace
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param workspace_id path string true "工作空间 ID"
|
||
// @Success 200 {array} dto.QuotaDTO
|
||
// @Router /workspaces/{workspace_id}/quotas [get]
|
||
func (h *WorkspaceHandler) GetWorkspaceQuotas(w http.ResponseWriter, r *http.Request) {
|
||
vars := mux.Vars(r)
|
||
workspaceID := vars["workspace_id"]
|
||
|
||
// 检查访问权限
|
||
if !h.canAccessWorkspace(w, r, workspaceID) {
|
||
return
|
||
}
|
||
|
||
quotas, err := h.workspaceService.GetQuotas(r.Context(), workspaceID)
|
||
if err != nil {
|
||
respondError(w, http.StatusInternalServerError, err.Error(), "")
|
||
return
|
||
}
|
||
|
||
respondSuccess(w, "", dto.QuotaDTOsFromEntities(quotas))
|
||
}
|
||
|
||
// SetWorkspaceQuotas 设置工作空间配额
|
||
// @Summary 设置工作空间配额
|
||
// @Description 设置指定工作空间的 CPU/GPU/GPU Memory 配额(Admin 专用)
|
||
// @Tags workspace
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param workspace_id path string true "工作空间 ID"
|
||
// @Param request body dto.SetQuotasRequest true "配额设置请求"
|
||
// @Success 200 {array} dto.QuotaDTO
|
||
// @Router /workspaces/{workspace_id}/quotas [put]
|
||
func (h *WorkspaceHandler) SetWorkspaceQuotas(w http.ResponseWriter, r *http.Request) {
|
||
// 检查权限(Admin)
|
||
if !h.requireAdmin(w, r) {
|
||
return
|
||
}
|
||
|
||
vars := mux.Vars(r)
|
||
workspaceID := vars["workspace_id"]
|
||
|
||
var req dto.SetQuotasRequest
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
respondError(w, http.StatusBadRequest, "Invalid request body", "")
|
||
return
|
||
}
|
||
|
||
quotas := make(map[entity.ResourceType]struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
})
|
||
|
||
if req.CPU != nil {
|
||
quotas[entity.ResourceCPU] = struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
}{req.CPU.HardLimit, req.CPU.SoftLimit}
|
||
}
|
||
if req.GPU != nil {
|
||
quotas[entity.ResourceGPU] = struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
}{req.GPU.HardLimit, req.GPU.SoftLimit}
|
||
}
|
||
if req.GPUMemory != nil {
|
||
quotas[entity.ResourceGPUMemory] = struct {
|
||
HardLimit float64
|
||
SoftLimit float64
|
||
}{req.GPUMemory.HardLimit, req.GPUMemory.SoftLimit}
|
||
}
|
||
|
||
if err := h.workspaceService.SetQuotas(r.Context(), workspaceID, quotas); err != nil {
|
||
respondError(w, http.StatusBadRequest, err.Error(), "")
|
||
return
|
||
}
|
||
|
||
// 返回更新后的配额
|
||
updatedQuotas, _ := h.workspaceService.GetQuotas(r.Context(), workspaceID)
|
||
respondSuccess(w, "", dto.QuotaDTOsFromEntities(updatedQuotas))
|
||
}
|
||
|
||
// requireAdmin 检查是否为 Admin
|
||
func (h *WorkspaceHandler) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
|
||
userRole := r.Header.Get("X-User-Role")
|
||
if userRole != string(entity.RoleAdmin) {
|
||
respondError(w, http.StatusForbidden, "Admin access required", "")
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// canAccessWorkspace 检查是否可以访问工作空间
|
||
func (h *WorkspaceHandler) canAccessWorkspace(w http.ResponseWriter, r *http.Request, workspaceID string) bool {
|
||
userRole := r.Header.Get("X-User-Role")
|
||
userWorkspaceID := r.Header.Get("X-Workspace-ID")
|
||
|
||
// Admin 可以访问所有
|
||
if userRole == string(entity.RoleAdmin) {
|
||
return true
|
||
}
|
||
|
||
// 普通用户只能访问自己的 workspace
|
||
if userWorkspaceID != workspaceID {
|
||
respondError(w, http.StatusForbidden, "Access denied", "")
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} |