package builder import ( "context" "encoding/json" "fmt" "gopkg.in/yaml.v3" "vanderlande.com/ittp/appstack/rig-operator/api/v1alpha1" "vanderlande.com/ittp/appstack/rig-operator/internal/provider" ) // ChartConfig holds the helm settings extracted from the YAML _defaults // The Controller needs this to know WHICH chart to fetch. type ChartConfig struct { Repo string Name string Version string } type MasterBuilder struct { strategy provider.Strategy baseTemplate []byte chartConfig ChartConfig } func NewMasterBuilder(strategy provider.Strategy, baseTemplate []byte) *MasterBuilder { b := &MasterBuilder{ strategy: strategy, baseTemplate: baseTemplate, // Safe defaults chartConfig: ChartConfig{ Name: "oci://ghcr.io/rancherfederal/charts/rancher-cluster-templates", }, } return b } // GetChartConfig returns the chart details found in the template. func (b *MasterBuilder) GetChartConfig() ChartConfig { return b.chartConfig } // Build orchestrates the values generation process func (b *MasterBuilder) Build(ctx context.Context, cbp *v1alpha1.ClusterBlueprint, credentialSecret string) (map[string]interface{}, error) { values := make(map[string]interface{}) if err := yaml.Unmarshal(b.baseTemplate, &values); err != nil { return nil, fmt.Errorf("failed to unmarshal base template: %w", err) } // 1. Extract Chart Config from _defaults (Legacy Logic Ported) // We do this so the Controller knows what version to install. if defaults, ok := values["_defaults"].(map[string]interface{}); ok { if chartCfg, ok := defaults["helmChart"].(map[string]interface{}); ok { if v, ok := chartCfg["repo"].(string); ok { b.chartConfig.Repo = v } if v, ok := chartCfg["name"].(string); ok { b.chartConfig.Name = v } if v, ok := chartCfg["version"].(string); ok { b.chartConfig.Version = v } } } // 2. Generate Node Pools (Delegated to Strategy) // [DIFFERENCE]: We don't loop here. The Strategy knows how to map CBP -> Provider NodePools. nodePools, err := b.strategy.GenerateNodePools(ctx, cbp) if err != nil { return nil, fmt.Errorf("strategy failed to generate node pools: %w", err) } // 3. Get Global Overrides (Delegated to Strategy) // [DIFFERENCE]: We don't hardcode "cloud_provider_name" here. The Strategy returns it. overrides, err := b.strategy.GetGlobalOverrides(ctx, cbp, credentialSecret) if err != nil { return nil, fmt.Errorf("strategy failed to get global overrides: %w", err) } // 4. Inject Logic into the Helm Structure if clusterMap, ok := values["cluster"].(map[string]interface{}); ok { clusterMap["name"] = cbp.Name if configMap, ok := clusterMap["config"].(map[string]interface{}); ok { configMap["kubernetesVersion"] = cbp.Spec.KubernetesVersion // Ensure globalConfig exists if _, ok := configMap["globalConfig"]; !ok { configMap["globalConfig"] = make(map[string]interface{}) } globalConfig := configMap["globalConfig"].(map[string]interface{}) // Inject Overrides for k, v := range overrides { // A. Handle specific Global Config keys if k == "cloud_provider_name" || k == "cloud_provider_config" { globalConfig[k] = v continue } // B. Handle Chart Values (CCM/CSI Addons) if k == "chartValues" { if existingChartVals, ok := configMap["chartValues"].(map[string]interface{}); ok { if newChartVals, ok := v.(map[string]interface{}); ok { for ck, cv := range newChartVals { existingChartVals[ck] = cv } } } else { configMap["chartValues"] = v } continue } // C. Default: Inject at Root level values[k] = v } } } // 5. Inject Node Pools // We marshal/unmarshal to ensure JSON tags from the Strategy structs are respected tempJSON, _ := json.Marshal(nodePools) var cleanNodePools interface{} _ = json.Unmarshal(tempJSON, &cleanNodePools) values["nodepools"] = cleanNodePools // 6. Cleanup internal keys delete(values, "_defaults") return values, nil }