Files
ocdp-go/backend/internal/domain/service/storage_service.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

208 lines
6.2 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.

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
}