- 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
215 lines
5.7 KiB
Go
215 lines
5.7 KiB
Go
package bootstrap
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
|
||
"github.com/ocdp/cluster-service/internal/adapter/output"
|
||
"github.com/ocdp/cluster-service/internal/domain/entity"
|
||
"github.com/ocdp/cluster-service/internal/pkg/password"
|
||
)
|
||
|
||
// Seeder 预注入管理器
|
||
type Seeder struct {
|
||
repos *output.Repositories
|
||
passwordHasher *password.Hasher
|
||
config *BootstrapConfig
|
||
}
|
||
|
||
// NewSeeder 创建预注入管理器
|
||
func NewSeeder(repos *output.Repositories, passwordHasher *password.Hasher, config *BootstrapConfig) *Seeder {
|
||
return &Seeder{
|
||
repos: repos,
|
||
passwordHasher: passwordHasher,
|
||
config: config,
|
||
}
|
||
}
|
||
|
||
// SeedAll 执行所有预注入
|
||
func (s *Seeder) SeedAll(ctx context.Context) error {
|
||
if !s.config.Enabled {
|
||
log.Println(" ℹ️ Bootstrap seeding is disabled")
|
||
return nil
|
||
}
|
||
|
||
log.Println(" 🌱 Starting bootstrap seeding...")
|
||
|
||
// 1. 注入用户
|
||
if err := s.seedUsers(ctx); err != nil {
|
||
return fmt.Errorf("failed to seed users: %w", err)
|
||
}
|
||
|
||
// 2. 注入 Registries
|
||
if err := s.seedRegistries(ctx); err != nil {
|
||
return fmt.Errorf("failed to seed registries: %w", err)
|
||
}
|
||
|
||
// 3. 注入 Clusters
|
||
if err := s.seedClusters(ctx); err != nil {
|
||
return fmt.Errorf("failed to seed clusters: %w", err)
|
||
}
|
||
|
||
log.Println(" ✅ Bootstrap seeding completed")
|
||
return nil
|
||
}
|
||
|
||
// seedUsers 注入用户
|
||
func (s *Seeder) seedUsers(ctx context.Context) error {
|
||
if len(s.config.Users) == 0 {
|
||
log.Println(" ↳ No users to seed")
|
||
return nil
|
||
}
|
||
|
||
log.Printf(" ↳ Seeding %d user(s)...", len(s.config.Users))
|
||
|
||
for _, userSeed := range s.config.Users {
|
||
// 检查用户是否已存在
|
||
existingUser, _ := s.repos.UserRepo.GetByUsername(ctx, userSeed.Username)
|
||
if existingUser != nil {
|
||
log.Printf(" ⊙ User '%s' already exists, skipping", userSeed.Username)
|
||
continue
|
||
}
|
||
|
||
// 哈希密码
|
||
passwordHash, err := s.passwordHasher.Hash(userSeed.Password)
|
||
if err != nil {
|
||
log.Printf(" ✗ Failed to hash password for user '%s': %v", userSeed.Username, err)
|
||
continue
|
||
}
|
||
|
||
// 创建用户
|
||
user := entity.NewUser(userSeed.Username, passwordHash, userSeed.Email)
|
||
user.ID = uuid.New().String()
|
||
if userSeed.Role != "" {
|
||
user.Role = userSeed.Role
|
||
}
|
||
if user.Role == "admin" {
|
||
user.WorkspaceID = entity.DefaultWorkspaceID
|
||
}
|
||
|
||
if err := s.repos.UserRepo.Create(ctx, user); err != nil {
|
||
log.Printf(" ✗ Failed to create user '%s': %v", userSeed.Username, err)
|
||
continue
|
||
}
|
||
|
||
log.Printf(" ✓ User '%s' created", userSeed.Username)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// seedRegistries 注入 Registries
|
||
func (s *Seeder) seedRegistries(ctx context.Context) error {
|
||
if len(s.config.Registries) == 0 {
|
||
log.Println(" ↳ No registries to seed")
|
||
return nil
|
||
}
|
||
|
||
log.Printf(" ↳ Seeding %d registry(ies)...", len(s.config.Registries))
|
||
|
||
ownerID := s.bootstrapOwnerID(ctx)
|
||
for _, registrySeed := range s.config.Registries {
|
||
// 检查 Registry 是否已存在
|
||
existingRegistry, _ := s.repos.RegistryRepo.GetByName(ctx, registrySeed.Name)
|
||
if existingRegistry != nil {
|
||
log.Printf(" ⊙ Registry '%s' already exists, skipping", registrySeed.Name)
|
||
continue
|
||
}
|
||
|
||
// 创建 Registry
|
||
registry := &entity.Registry{
|
||
ID: uuid.New().String(),
|
||
Name: registrySeed.Name,
|
||
WorkspaceID: entity.DefaultWorkspaceID,
|
||
OwnerID: ownerID,
|
||
Visibility: "global_shared",
|
||
URL: registrySeed.URL,
|
||
Description: registrySeed.Description,
|
||
Username: registrySeed.Username,
|
||
Password: registrySeed.Password,
|
||
Insecure: registrySeed.Insecure,
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
if err := s.repos.RegistryRepo.Create(ctx, registry); err != nil {
|
||
log.Printf(" ✗ Failed to create registry '%s': %v", registrySeed.Name, err)
|
||
continue
|
||
}
|
||
|
||
log.Printf(" ✓ Registry '%s' created (credentials encrypted)", registrySeed.Name)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// seedClusters 注入 Clusters
|
||
func (s *Seeder) seedClusters(ctx context.Context) error {
|
||
if len(s.config.Clusters) == 0 {
|
||
log.Println(" ↳ No clusters to seed")
|
||
return nil
|
||
}
|
||
|
||
log.Printf(" ↳ Seeding %d cluster(s)...", len(s.config.Clusters))
|
||
|
||
ownerID := s.bootstrapOwnerID(ctx)
|
||
for _, clusterSeed := range s.config.Clusters {
|
||
// 检查 Cluster 是否已存在
|
||
existingCluster, _ := s.repos.ClusterRepo.GetByName(ctx, clusterSeed.Name)
|
||
if existingCluster != nil {
|
||
log.Printf(" ⊙ Cluster '%s' already exists, skipping", clusterSeed.Name)
|
||
continue
|
||
}
|
||
|
||
// 创建 Cluster
|
||
cluster := &entity.Cluster{
|
||
ID: uuid.New().String(),
|
||
Name: clusterSeed.Name,
|
||
WorkspaceID: entity.DefaultWorkspaceID,
|
||
OwnerID: ownerID,
|
||
Visibility: "global_shared",
|
||
Host: clusterSeed.Host,
|
||
Description: clusterSeed.Description,
|
||
CAData: clusterSeed.CAData,
|
||
CertData: clusterSeed.CertData,
|
||
KeyData: clusterSeed.KeyData,
|
||
Token: clusterSeed.Token,
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
if err := s.repos.ClusterRepo.Create(ctx, cluster); err != nil {
|
||
log.Printf(" ✗ Failed to create cluster '%s': %v", clusterSeed.Name, err)
|
||
continue
|
||
}
|
||
|
||
log.Printf(" ✓ Cluster '%s' created (credentials encrypted)", clusterSeed.Name)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (s *Seeder) bootstrapOwnerID(ctx context.Context) string {
|
||
for _, userSeed := range s.config.Users {
|
||
if userSeed.Role == "admin" {
|
||
if user, err := s.repos.UserRepo.GetByUsername(ctx, userSeed.Username); err == nil && user != nil {
|
||
return user.ID
|
||
}
|
||
}
|
||
}
|
||
users, err := s.repos.UserRepo.List(ctx)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
for _, user := range users {
|
||
if user.Role == "admin" {
|
||
return user.ID
|
||
}
|
||
}
|
||
return ""
|
||
}
|