package bootstrap import ( "encoding/json" "fmt" "os" "path/filepath" "sort" "strconv" "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"` Role string `json:"role"` } // 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. 环境变量 BOOTSTRAP_* (root .env / container env) // 3. Mock 模式: 配置文件 config/bootstrap.json // 4. 未提供任何 bootstrap 配置时禁用预注入 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 } if config, ok := loadBootstrapConfigFromEnv(); ok { 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. 真实模式: 未显式配置时不预注入任何数据 return GetDefaultBootstrapConfig(), nil } func loadBootstrapConfigFromEnv() (*BootstrapConfig, bool) { if !hasBootstrapEnv() { return nil, false } config := &BootstrapConfig{ Enabled: true, Users: make([]UserSeed, 0, 1), Registries: make([]RegistrySeed, 0, 1), Clusters: make([]ClusterSeed, 0), } adminUser := strings.TrimSpace(os.Getenv("BOOTSTRAP_ADMIN_USER")) adminPass := strings.TrimSpace(os.Getenv("BOOTSTRAP_ADMIN_PASS")) if adminUser != "" && adminPass != "" { config.Users = append(config.Users, UserSeed{ Username: adminUser, Password: adminPass, Email: getEnv("BOOTSTRAP_ADMIN_EMAIL", adminUser+"@example.local"), Role: "admin", }) } if registryURL := os.Getenv("BOOTSTRAP_REGISTRY_URL"); registryURL != "" { registryUser := getEnv("BOOTSTRAP_REGISTRY_ROBOT_USER", getEnv("BOOTSTRAP_REGISTRY_USER", "")) registryPass := getEnv("BOOTSTRAP_REGISTRY_ROBOT_PASS", getEnv("BOOTSTRAP_REGISTRY_PASS", "")) config.Registries = append(config.Registries, RegistrySeed{ Name: getEnv("BOOTSTRAP_REGISTRY_NAME", "harbor"), URL: registryURL, Description: getEnv("BOOTSTRAP_REGISTRY_DESC", ""), Username: registryUser, Password: registryPass, Insecure: parseBoolEnv("BOOTSTRAP_REGISTRY_INSECURE", false), }) } enableClusters := parseBoolEnv("BOOTSTRAP_ENABLE_CLUSTERS", false) || os.Getenv("BOOTSTRAP_CLUSTERS") != "" if enableClusters { for _, clusterName := range discoverBootstrapClusters() { prefix := "BOOTSTRAP_CLUSTER_" + normalizeEnvName(clusterName) + "_" host := os.Getenv(prefix + "HOST") if host == "" { continue } config.Clusters = append(config.Clusters, ClusterSeed{ Name: strings.ToLower(clusterName), Host: host, Description: os.Getenv(prefix + "DESC"), CAData: os.Getenv(prefix + "CA"), CertData: os.Getenv(prefix + "CERT"), KeyData: os.Getenv(prefix + "KEY"), Token: os.Getenv(prefix + "TOKEN"), }) } } return config, true } func hasBootstrapEnv() bool { for _, env := range os.Environ() { if strings.HasPrefix(env, "BOOTSTRAP_") { return true } } return false } func discoverBootstrapClusters() []string { names := make(map[string]struct{}) if configured := os.Getenv("BOOTSTRAP_CLUSTERS"); configured != "" { for _, name := range strings.Split(configured, ",") { name = strings.TrimSpace(name) if name != "" { names[normalizeEnvName(name)] = struct{}{} } } } for _, env := range os.Environ() { key, _, ok := strings.Cut(env, "=") if !ok || !strings.HasPrefix(key, "BOOTSTRAP_CLUSTER_") || !strings.HasSuffix(key, "_HOST") { continue } name := strings.TrimSuffix(strings.TrimPrefix(key, "BOOTSTRAP_CLUSTER_"), "_HOST") if name != "" { names[name] = struct{}{} } } result := make([]string, 0, len(names)) for name := range names { result = append(result, name) } sort.Strings(result) return result } func normalizeEnvName(name string) string { replacer := strings.NewReplacer("-", "_", ".", "_", " ", "_") return strings.ToUpper(replacer.Replace(strings.TrimSpace(name))) } func parseBoolEnv(key string, defaultValue bool) bool { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return defaultValue } parsed, err := strconv.ParseBool(value) if err != nil { return defaultValue } return parsed } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // GetDefaultBootstrapConfig 返回安全的空默认配置。 // // 这里不能包含真实或示例账号密码、Registry 或集群凭据。预注入数据必须来自 // BOOTSTRAP_CONFIG_JSON、BOOTSTRAP_* 环境变量,或显式提供的 bootstrap 配置文件。 func GetDefaultBootstrapConfig() *BootstrapConfig { return &BootstrapConfig{ Enabled: false, Users: []UserSeed{}, Registries: []RegistrySeed{}, Clusters: []ClusterSeed{}, } }