package service import ( "context" "errors" "fmt" "strings" "github.com/ocdp/cluster-service/internal/domain/entity" "github.com/ocdp/cluster-service/internal/domain/repository" ) var ( ErrStorageNotFound = errors.New("storage backend not found") ErrStorageExists = errors.New("storage backend already exists") ) // StorageResolution 存储分层解析结果 type StorageResolution struct { Storage *entity.StorageBackend // 最终选中的 storage ValuesYAML string // 转换为 YAML 的 values Source string // 来源: "workspace", "cluster", "shared" } // StorageService 存储后端领域服务 type StorageService struct { storageRepo repository.StorageRepository } // NewStorageService 创建存储后端服务 func NewStorageService(storageRepo repository.StorageRepository) *StorageService { return &StorageService{ storageRepo: storageRepo, } } // Create 创建存储后端 func (s *StorageService) Create( ctx context.Context, workspaceID, ownerID, name string, storageType entity.StorageType, config entity.StorageConfig, description string, isDefault, isShared bool, clusterID string, ) (*entity.StorageBackend, error) { // 检查名称是否已存在(同一 workspace 或同一 cluster 下不能重复) existing, _ := s.storageRepo.GetByName(ctx, workspaceID, name) if existing != nil { return nil, ErrStorageExists } var storage *entity.StorageBackend if clusterID != "" { storage = entity.NewClusterStorageBackend(workspaceID, clusterID, ownerID, name, storageType, config) } else { storage = entity.NewStorageBackend(workspaceID, ownerID, name, storageType, config) } storage.Description = description storage.IsDefault = isDefault storage.IsShared = isShared if err := s.storageRepo.Create(ctx, storage); err != nil { return nil, err } return storage, nil } // GetByID 获取存储后端 func (s *StorageService) GetByID(ctx context.Context, id string) (*entity.StorageBackend, error) { storage, err := s.storageRepo.GetByID(ctx, id) if err != nil { return nil, ErrStorageNotFound } return storage, nil } // GetByWorkspace 获取工作空间的所有存储后端 func (s *StorageService) GetByWorkspace(ctx context.Context, workspaceID string) ([]*entity.StorageBackend, error) { return s.storageRepo.GetByWorkspace(ctx, workspaceID) } // GetShared 获取所有共享存储后端 func (s *StorageService) GetShared(ctx context.Context) ([]*entity.StorageBackend, error) { return s.storageRepo.GetShared(ctx) } // GetDefault 获取工作空间的默认存储后端 func (s *StorageService) GetDefault(ctx context.Context, workspaceID string) (*entity.StorageBackend, error) { return s.storageRepo.GetDefault(ctx, workspaceID) } // Update 更新存储后端 func (s *StorageService) Update( ctx context.Context, id, name, description string, storageType entity.StorageType, config entity.StorageConfig, isDefault, isShared bool, ) (*entity.StorageBackend, error) { storage, err := s.storageRepo.GetByID(ctx, id) if err != nil { return nil, ErrStorageNotFound } if name != "" { storage.Name = name } storage.Description = description storage.Type = storageType storage.Config = config storage.IsDefault = isDefault storage.IsShared = isShared if err := s.storageRepo.Update(ctx, storage); err != nil { return nil, err } return storage, nil } // Delete 删除存储后端 func (s *StorageService) Delete(ctx context.Context, id string) error { return s.storageRepo.Delete(ctx, id) } // List 列出所有存储后端(管理员用) func (s *StorageService) List(ctx context.Context) ([]*entity.StorageBackend, error) { return s.storageRepo.List(ctx) } // ResolveStorageConfig 分层解析存储配置 // 优先级:User Override > Workspace Default > Cluster Default > Shared Default func (s *StorageService) ResolveStorageConfig( ctx context.Context, clusterID, workspaceID string, ) (*StorageResolution, error) { // 1. 查找 workspace-level 默认存储 if workspaceID != "" { wsStorage, err := s.storageRepo.GetDefault(ctx, workspaceID) if err == nil && wsStorage != nil { yaml, _ := storageToValuesYAML(wsStorage) return &StorageResolution{ Storage: wsStorage, ValuesYAML: yaml, Source: "workspace", }, nil } } // 2. 查找 cluster-level 默认存储 if clusterID != "" { clusterStorage, err := s.storageRepo.GetDefaultByCluster(ctx, clusterID) if err == nil && clusterStorage != nil { yaml, _ := storageToValuesYAML(clusterStorage) return &StorageResolution{ Storage: clusterStorage, ValuesYAML: yaml, Source: "cluster", }, nil } } // 3. 查找 shared 默认存储 sharedStorages, err := s.storageRepo.GetShared(ctx) if err == nil { for _, s := range sharedStorages { if s.IsDefault { yaml, _ := storageToValuesYAML(s) return &StorageResolution{ Storage: s, ValuesYAML: yaml, Source: "shared", }, nil } } } return nil, nil } // storageToValuesYAML 将 storage config 转换为 values.yaml 格式 func storageToValuesYAML(storage *entity.StorageBackend) (string, error) { if storage == nil { return "", nil } switch storage.Type { case entity.StorageTypeNFS: if storage.Config.NFS != nil { // Format as nfs-server/path storageClass so Helm charts like bitnami/nginx // can use: persistence.storageClass: "nfs-server/path" nfsSC := fmt.Sprintf("nfs-%s-%s", storage.Config.NFS.Server, strings.TrimPrefix(storage.Config.NFS.Path, "/")) return fmt.Sprintf("persistence:\n enabled: true\n storageClass: \"%s\"\n existingClaim: \"\"\n mountOptions:\n - hard\n - nfsvers=4.1\n dataSource: {}\n# NFS Server: %s\n# NFS Path: %s", nfsSC, storage.Config.NFS.Server, storage.Config.NFS.Path), nil } case entity.StorageTypePV: if storage.Config.PV != nil { return fmt.Sprintf("persistence:\n enabled: true\n storageClass: \"%s\"\n size: %s\n accessModes: %v\n existingClaim: \"\"", storage.Config.PV.StorageClassName, storage.Config.PV.Capacity, storage.Config.PV.AccessModes), nil } case entity.StorageTypeHostPath: if storage.Config.HostPath != nil { return fmt.Sprintf("persistence:\n enabled: true\n hostPath: \"%s\"\n existingClaim: \"\"", storage.Config.HostPath.Path), nil } } return "", nil }