diff --git a/controllers/export_test.go b/controllers/export_test.go index cd3a1a9f..1dad0ed6 100644 --- a/controllers/export_test.go +++ b/controllers/export_test.go @@ -122,6 +122,7 @@ var ( GetInstantiatedChart = getInstantiatedChart InstantiateTemplateValues = instantiateTemplateValues + FecthClusterObjects = fecthClusterObjects IsCluterSummaryProvisioned = isCluterSummaryProvisioned IsNamespaced = isNamespaced diff --git a/controllers/handlers_helm.go b/controllers/handlers_helm.go index 5c359be6..9e3e9750 100644 --- a/controllers/handlers_helm.go +++ b/controllers/handlers_helm.go @@ -2426,8 +2426,14 @@ func getInstantiatedValues(ctx context.Context, clusterSummary *configv1beta1.Cl mgmtConfig := getManagementClusterConfig() mgmtClient := getManagementClusterClient() + objects, err := fecthClusterObjects(ctx, mgmtConfig, mgmtClient, clusterSummary.Spec.ClusterNamespace, + clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + if err != nil { + return nil, err + } + instantiatedValues, err := instantiateTemplateValues(ctx, mgmtConfig, mgmtClient, - clusterSummary, requestedChart.ChartName, requestedChart.Values, mgmtResources, logger) + clusterSummary, requestedChart.ChartName, requestedChart.Values, objects, mgmtResources, logger) if err != nil { return nil, err } @@ -2446,7 +2452,7 @@ func getInstantiatedValues(ctx context.Context, clusterSummary *configv1beta1.Cl for k := range valuesFrom { instantiatedValuesFrom, err := instantiateTemplateValues(ctx, mgmtConfig, mgmtClient, - clusterSummary, requestedChart.ChartName, valuesFrom[k], mgmtResources, logger) + clusterSummary, requestedChart.ChartName, valuesFrom[k], objects, mgmtResources, logger) if err != nil { return nil, err } @@ -3924,25 +3930,22 @@ func getInstantiatedChart(ctx context.Context, clusterSummary *configv1beta1.Clu currentChart *configv1beta1.HelmChart, mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger) (*configv1beta1.HelmChart, error) { - // Marshal the struct to YAML - jsonData, err := yaml.Marshal(*currentChart) - if err != nil { - return nil, err - } + // Create a deep copy of the chart to avoid modifying the original. + instantiatedChart := currentChart.DeepCopy() - instantiatedChartString, err := instantiateTemplateValues(ctx, getManagementClusterConfig(), - getManagementClusterClient(), clusterSummary, currentChart.ChartName, string(jsonData), mgmtResources, logger) + objects, err := fecthClusterObjects(ctx, getManagementClusterConfig(), getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) if err != nil { return nil, err } - var instantiatedChart configv1beta1.HelmChart - err = yaml.Unmarshal([]byte(instantiatedChartString), &instantiatedChart) - if err != nil { + // Call the new recursive helper function to instantiate all fields. + if err := instantiateStructFields(ctx, getManagementClusterConfig(), getManagementClusterClient(), + instantiatedChart, clusterSummary, objects, mgmtResources, logger); err != nil { return nil, err } - return &instantiatedChart, nil + return instantiatedChart, nil } func canManageChart(ctx context.Context, c client.Client, clusterSummary *configv1beta1.ClusterSummary, diff --git a/controllers/handlers_helm_test.go b/controllers/handlers_helm_test.go index 84317045..04199ecb 100644 --- a/controllers/handlers_helm_test.go +++ b/controllers/handlers_helm_test.go @@ -518,11 +518,11 @@ var _ = Describe("HandlersHelm", func() { RepositoryName: randomString(), HelmChartAction: configv1beta1.HelmChartActionInstall, } - helmChart.ChartVersion = `{{$version := index .Cluster.metadata.labels "k8s-version" }}{{if eq $version "1.20"}}23.4.0 -{{else if eq $version "1.22"}}24.1.0 -{{else if eq $version "1.25"}}25.0.2 -{{ else }}23.4.0 -{{end}}` + helmChart.ChartVersion = `{{$version := index .Cluster.metadata.labels "k8s-version" }}{{- if eq $version "1.20"}}23.4.0 +{{- else if eq $version "1.22"}}24.1.0 +{{- else if eq $version "1.25"}}25.0.2 +{{- else }}23.4.0 +{{- end}}` clusterSummary.Namespace = defaulNamespace clusterSummary.Spec.ClusterNamespace = defaulNamespace diff --git a/controllers/handlers_kustomize.go b/controllers/handlers_kustomize.go index aab0a9cb..d88f9b73 100644 --- a/controllers/handlers_kustomize.go +++ b/controllers/handlers_kustomize.go @@ -493,9 +493,15 @@ func instantiateKustomizeSubstituteValues(ctx context.Context, clusterSummary *c requestorName := clusterSummary.Namespace + clusterSummary.Name + "kustomize" + objects, err := fecthClusterObjects(ctx, getManagementClusterConfig(), getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + if err != nil { + return nil, err + } + instantiatedValue, err := instantiateTemplateValues(ctx, getManagementClusterConfig(), getManagementClusterClient(), - clusterSummary, requestorName, stringifiedValues, mgmtResources, logger) + clusterSummary, requestorName, stringifiedValues, objects, mgmtResources, logger) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to instantiate values %v", err)) return nil, err @@ -566,9 +572,15 @@ func deployKustomizeRef(ctx context.Context, c client.Client, remoteRestConfig * defer os.RemoveAll(tmpDir) + objects, err := fecthClusterObjects(ctx, getManagementClusterConfig(), getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + if err != nil { + return nil, nil, err + } + // Path can be expressed as a template and instantiate using Cluster fields. instantiatedPath, err := instantiateTemplateValues(ctx, getManagementClusterConfig(), getManagementClusterClient(), - clusterSummary, clusterSummary.GetName(), kustomizationRef.Path, nil, logger) + clusterSummary, clusterSummary.GetName(), kustomizationRef.Path, objects, nil, logger) if err != nil { return nil, nil, err } @@ -1024,7 +1036,8 @@ func extractTarGz(src, dest string) error { func instantiateResourceWithSubstituteValues(templateName string, resource []byte, substituteValues map[string]string, useTxtFuncMap bool, logger logr.Logger) ([]byte, error) { - tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(funcmap.SveltosFuncMap(useTxtFuncMap)).Parse(string(resource)) + tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(funcmap.SveltosFuncMap(useTxtFuncMap)). + Parse(string(resource)) if err != nil { return nil, err } diff --git a/controllers/handlers_utils.go b/controllers/handlers_utils.go index ee3f3dce..9a465103 100644 --- a/controllers/handlers_utils.go +++ b/controllers/handlers_utils.go @@ -120,9 +120,15 @@ func deployContentOfSource(ctx context.Context, deployingToMgmtCluster bool, des defer os.RemoveAll(tmpDir) + objects, err := fecthClusterObjects(ctx, getManagementClusterConfig(), getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + if err != nil { + return nil, err + } + // Path can be expressed as a template and instantiate using Cluster fields. instantiatedPath, err := instantiateTemplateValues(ctx, getManagementClusterConfig(), getManagementClusterClient(), - clusterSummary, clusterSummary.GetName(), path, nil, logger) + clusterSummary, clusterSummary.GetName(), path, objects, nil, logger) if err != nil { return nil, err } @@ -526,12 +532,18 @@ func collectContent(ctx context.Context, clusterSummary *configv1beta1.ClusterSu policies := make([]*unstructured.Unstructured, 0, len(data)) + objects, err := fecthClusterObjects(ctx, getManagementClusterConfig(), getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + if err != nil { + return nil, err + } + for k := range data { section := data[k] if instantiateTemplate { instance, err := instantiateTemplateValues(ctx, getManagementClusterConfig(), getManagementClusterClient(), - clusterSummary, clusterSummary.GetName(), section, mgmtResources, logger) + clusterSummary, clusterSummary.GetName(), section, objects, mgmtResources, logger) if err != nil { logger.Error(err, fmt.Sprintf("failed to instantiate policy from Data %.100s", section)) return nil, err @@ -1292,9 +1304,15 @@ func initiatePatches(ctx context.Context, clusterSummary *configv1beta1.ClusterS instantiatedPatches = clusterSummary.Spec.ClusterProfileSpec.Patches + objects, err := fecthClusterObjects(ctx, getManagementClusterConfig(), getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + if err != nil { + return nil, err + } + for k := range instantiatedPatches { instantiatedPatch, err := instantiateTemplateValues(ctx, getManagementClusterConfig(), getManagementClusterClient(), - clusterSummary, requestor, instantiatedPatches[k].Patch, mgmtResources, logger) + clusterSummary, requestor, instantiatedPatches[k].Patch, objects, mgmtResources, logger) if err != nil { return nil, err } diff --git a/controllers/template_instantiation.go b/controllers/template_instantiation.go index b28e913f..da42cef8 100644 --- a/controllers/template_instantiation.go +++ b/controllers/template_instantiation.go @@ -17,6 +17,7 @@ import ( "bytes" "context" "fmt" + "reflect" "strings" "text/template" @@ -155,16 +156,73 @@ func fecthClusterObjects(ctx context.Context, config *rest.Config, c client.Clie return result, nil } -func instantiateTemplateValues(ctx context.Context, config *rest.Config, c client.Client, //nolint: funlen,maintidx // adding few closures - clusterSummary *configv1beta1.ClusterSummary, requestorName, values string, - mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger) (string, error) { +// instantiateGenericField is a helper function to instantiate a single field, regardless of type. +func instantiateGenericField(ctx context.Context, config *rest.Config, c client.Client, + field interface{}, clusterSummary *configv1beta1.ClusterSummary, objects *currentClusterObjects, + mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger) (interface{}, error) { - objects, err := fecthClusterObjects(ctx, config, c, clusterSummary.Spec.ClusterNamespace, - clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + // Marshal the field's value to a YAML string. + var buf bytes.Buffer + encoder := yaml.NewEncoder(&buf) + err := encoder.Encode(field) if err != nil { - return "", err + return nil, fmt.Errorf("failed to marshal field: %w", err) } + // Instantiate the YAML string with the template engine. + instantiatedString, err := instantiateTemplateValues(ctx, config, c, + clusterSummary, "chart-name", buf.String(), objects, mgmtResources, logger) + if err != nil { + return nil, fmt.Errorf("failed to instantiate field: %w", err) + } + + // Unmarshal the instantiated string back into a new variable of the same type as the original field. + result := reflect.New(reflect.TypeOf(field)).Interface() + err = yaml.Unmarshal([]byte(instantiatedString), result) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal instantiated field: %w", err) + } + + return result, nil +} + +// instantiateStructFields is a recursive helper function to instantiate all string fields in a struct. +func instantiateStructFields(ctx context.Context, config *rest.Config, c client.Client, s interface{}, + clusterSummary *configv1beta1.ClusterSummary, objects *currentClusterObjects, + mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger) error { + + value := reflect.ValueOf(s) + + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + + if value.Kind() != reflect.Struct { + return nil + } + + for i := 0; i < value.NumField(); i++ { + field := value.Field(i) + + if field.CanSet() { + // Use the generic instantiation helper for all fields that can be set. + instantiatedValue, err := instantiateGenericField(ctx, config, c, field.Interface(), clusterSummary, + objects, mgmtResources, logger) + if err != nil { + return fmt.Errorf("failed to instantiate field '%s': %w", value.Type().Field(i).Name, err) + } + + // Set the instantiated value back to the field. + field.Set(reflect.ValueOf(instantiatedValue).Elem()) + } + } + return nil +} + +func instantiateTemplateValues(ctx context.Context, config *rest.Config, c client.Client, //nolint: funlen // adding few closures + clusterSummary *configv1beta1.ClusterSummary, requestorName, values string, objects *currentClusterObjects, + mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger) (string, error) { + if mgmtResources != nil { objects.MgmtResources = make(map[string]map[string]interface{}) for k := range mgmtResources { diff --git a/controllers/template_instantiation_test.go b/controllers/template_instantiation_test.go index a5e6702b..75971c89 100644 --- a/controllers/template_instantiation_test.go +++ b/controllers/template_instantiation_test.go @@ -102,8 +102,14 @@ var _ = Describe("Template instantiation", func() { controller: name: "{{ .Cluster.metadata.name }}-test"` + logger := textlogger.NewLogger(textlogger.NewConfig()) + + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, nil, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, nil, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring(fmt.Sprintf("%s-test", cluster.Name))) }) @@ -132,8 +138,13 @@ var _ = Describe("Template instantiation", func() { "Deployment": u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) value, err := strconv.Atoi(strings.ReplaceAll(result, "\n", "")) Expect(err).To(BeNil()) @@ -164,8 +175,13 @@ var _ = Describe("Template instantiation", func() { "Deployment": u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring("replicas: 7")) @@ -175,7 +191,7 @@ var _ = Describe("Template instantiation", func() { values = `{{ setField "Deployment" "spec.paused" false }}` result, err = controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring("paused: false")) @@ -186,7 +202,7 @@ var _ = Describe("Template instantiation", func() { values = fmt.Sprintf(`{{ setField "Deployment" "metadata.namespace" %q }}`, namespace) result, err = controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring(fmt.Sprintf("namespace: %s", namespace))) @@ -239,8 +255,13 @@ var _ = Describe("Template instantiation", func() { "Deployment": u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) modifiedDepl := &appsv1.Deployment{} @@ -275,8 +296,13 @@ var _ = Describe("Template instantiation", func() { "ConfigMap": u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).ToNot(ContainSubstring("replicas")) @@ -311,8 +337,13 @@ var _ = Describe("Template instantiation", func() { "Deployment": u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).ToNot(ContainSubstring("replicas")) @@ -362,8 +393,13 @@ var _ = Describe("Template instantiation", func() { "Deployment": u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring("replicas: 5")) Expect(result).To(ContainSubstring(fmt.Sprintf("namespace: %s", cluster.Namespace))) @@ -384,11 +420,16 @@ var _ = Describe("Template instantiation", func() { values := `valuesTemplate: | controller: name: "{{ .Cluster.metadata.name }}-test" - cidrs: {{ index .Cluster.spec.clusterNetwork.pods.cidrBlocks 0 }} + cidrs: {{ index .Cluster.spec.clusterNetwork.pods.cidrBlocks 0 }} ` + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, nil, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, nil, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring(fmt.Sprintf("%s-test", cluster.Name))) Expect(result).To(ContainSubstring(cluster.Spec.ClusterNetwork.Pods.CIDRBlocks[0])) @@ -434,8 +475,13 @@ valuesTemplate: | "Secret": &u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring(pwd)) }) @@ -482,8 +528,13 @@ valuesTemplate: | "Secret": &u, } + logger := textlogger.NewLogger(textlogger.NewConfig()) + objects, err := controllers.FecthClusterObjects(ctx, testEnv.Config, testEnv.GetClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, clusterSummary.Spec.ClusterType, logger) + Expect(err).To(BeNil()) + result, err := controllers.InstantiateTemplateValues(context.TODO(), testEnv.Config, testEnv.GetClient(), - clusterSummary, randomString(), values, mgmtResources, textlogger.NewLogger(textlogger.NewConfig())) + clusterSummary, randomString(), values, objects, mgmtResources, logger) Expect(err).To(BeNil()) Expect(result).To(ContainSubstring(pwd)) })