- 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
176 lines
5.3 KiB
Go
176 lines
5.3 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// BootstrapConfig 预注入配置
|
|
type BootstrapConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Users []UserSeed `json:"users"`
|
|
Registries []RegistrySeed `json:"registries"`
|
|
Clusters []ClusterSeed `json:"clusters"`
|
|
}
|
|
|
|
// UserSeed 用户预注入数据
|
|
type UserSeed struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
// RegistrySeed Registry 预注入数据
|
|
type RegistrySeed struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
Description string `json:"description"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Insecure bool `json:"insecure"`
|
|
}
|
|
|
|
// ClusterSeed Cluster 预注入数据
|
|
type ClusterSeed struct {
|
|
Name string `json:"name"`
|
|
Host string `json:"host"`
|
|
Description string `json:"description"`
|
|
CAData string `json:"ca_data"`
|
|
CertData string `json:"cert_data"`
|
|
KeyData string `json:"key_data"`
|
|
Token string `json:"token,omitempty"`
|
|
}
|
|
|
|
// LoadBootstrapConfig 加载预注入配置
|
|
// 支持从文件或环境变量加载
|
|
//
|
|
// 加载优先级:
|
|
// 1. 环境变量 BOOTSTRAP_CONFIG_JSON (最高优先级)
|
|
// 2. Mock 模式: 配置文件 config/bootstrap.json
|
|
// 3. 真实模式: GetDefaultBootstrapConfig() 从 .env 读取
|
|
func LoadBootstrapConfig() (*BootstrapConfig, error) {
|
|
// 1. 优先从环境变量加载
|
|
if configJSON := os.Getenv("BOOTSTRAP_CONFIG_JSON"); configJSON != "" {
|
|
var config BootstrapConfig
|
|
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
|
return nil, fmt.Errorf("failed to parse BOOTSTRAP_CONFIG_JSON: %w", err)
|
|
}
|
|
return &config, nil
|
|
}
|
|
|
|
// 2. 检查适配器模式
|
|
adapterMode := os.Getenv("ADAPTER_MODE")
|
|
|
|
// Mock 模式: 使用配置文件(假数据)
|
|
if adapterMode == "mock" {
|
|
configPath := os.Getenv("BOOTSTRAP_CONFIG_FILE")
|
|
if configPath == "" {
|
|
configPath = filepath.Join("config", "bootstrap.json")
|
|
}
|
|
|
|
// 检查文件是否存在
|
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
// 配置文件不存在,使用默认配置
|
|
return GetDefaultBootstrapConfig(), nil
|
|
}
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read bootstrap config file %s: %w", configPath, err)
|
|
}
|
|
|
|
var config BootstrapConfig
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to parse bootstrap config: %w", err)
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// 3. 真实模式 (mode 1, mode 2): 从 .env 读取
|
|
return GetDefaultBootstrapConfig(), nil
|
|
}
|
|
|
|
// GetDefaultBootstrapConfig 从 .env 加载 bootstrap 数据。
|
|
// 支持 BOOTSTRAP_CLUSTERS (逗号分隔的集群名) 以及每个集群的
|
|
// BOOTSTRAP_CLUSTER_<NAME>_HOST, _CA, _CERT, _KEY, _DESC。
|
|
// 支持 BOOTSTRAP_REGISTRY_* 环境变量。
|
|
// 支持 BOOTSTRAP_ADMIN_USER/PASS/EMAIL。
|
|
func GetDefaultBootstrapConfig() *BootstrapConfig {
|
|
// Load clusters from .env (comma-separated list of cluster names)
|
|
clusterStr := os.Getenv("BOOTSTRAP_CLUSTERS")
|
|
var clusterSeeds []ClusterSeed
|
|
if clusterStr != "" {
|
|
clusterNames := strings.Split(clusterStr, ",")
|
|
for _, name := range clusterNames {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
key := sanitizeEnvKey(name)
|
|
host := os.Getenv("BOOTSTRAP_CLUSTER_" + key + "_HOST")
|
|
ca := os.Getenv("BOOTSTRAP_CLUSTER_" + key + "_CA")
|
|
cert := os.Getenv("BOOTSTRAP_CLUSTER_" + key + "_CERT")
|
|
keyData := os.Getenv("BOOTSTRAP_CLUSTER_" + key + "_KEY")
|
|
desc := os.Getenv("BOOTSTRAP_CLUSTER_" + key + "_DESC")
|
|
if host != "" {
|
|
clusterSeeds = append(clusterSeeds, ClusterSeed{
|
|
Name: name,
|
|
Host: host,
|
|
Description: desc,
|
|
CAData: ca,
|
|
CertData: cert,
|
|
KeyData: keyData,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load registry from .env
|
|
var registrySeeds []RegistrySeed
|
|
regName := strings.TrimSpace(os.Getenv("BOOTSTRAP_REGISTRY_NAME"))
|
|
regURL := strings.TrimSpace(os.Getenv("BOOTSTRAP_REGISTRY_URL"))
|
|
if regName != "" && regURL != "" {
|
|
registrySeeds = append(registrySeeds, RegistrySeed{
|
|
Name: regName,
|
|
URL: regURL,
|
|
Description: strings.TrimSpace(os.Getenv("BOOTSTRAP_REGISTRY_DESC")),
|
|
Username: strings.TrimSpace(os.Getenv("BOOTSTRAP_REGISTRY_USER")),
|
|
Password: strings.TrimSpace(os.Getenv("BOOTSTRAP_REGISTRY_PASS")),
|
|
Insecure: strings.ToLower(strings.TrimSpace(os.Getenv("BOOTSTRAP_REGISTRY_INSECURE"))) == "true",
|
|
})
|
|
}
|
|
|
|
// Load users from .env
|
|
var userSeeds []UserSeed
|
|
adminUser := strings.TrimSpace(os.Getenv("BOOTSTRAP_ADMIN_USER"))
|
|
adminPass := strings.TrimSpace(os.Getenv("BOOTSTRAP_ADMIN_PASS"))
|
|
if adminUser != "" {
|
|
userSeeds = append(userSeeds, UserSeed{
|
|
Username: adminUser,
|
|
Password: adminPass,
|
|
Email: strings.TrimSpace(os.Getenv("BOOTSTRAP_ADMIN_EMAIL")),
|
|
})
|
|
}
|
|
|
|
return &BootstrapConfig{
|
|
Enabled: len(clusterSeeds) > 0 || len(registrySeeds) > 0 || len(userSeeds) > 0,
|
|
Users: userSeeds,
|
|
Registries: registrySeeds,
|
|
Clusters: clusterSeeds,
|
|
}
|
|
}
|
|
|
|
// sanitizeEnvKey converts "my-cluster" to "MY_CLUSTER" for env var names.
|
|
func sanitizeEnvKey(name string) string {
|
|
s := strings.Map(func(r rune) rune {
|
|
if r == '-' || r == ' ' {
|
|
return '_'
|
|
}
|
|
return r
|
|
}, name)
|
|
return strings.ToUpper(s)
|
|
} |