From 16cd04451f3f3c5df0707ae6ae47bf438e392b76 Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Thu, 8 May 2025 17:17:55 +0200 Subject: [PATCH] (feat): TemplateResourceRefs template instantion TemplateResourceRefs namespace/name can be expressed as template. This was possible even before this PR. Before this PR though only cluster namespace/name/kind were used to instantiate the template. Now any cluster field can be used. --- controllers/clustersummary_controller.go | 4 +- controllers/clustersummary_watchers.go | 12 +-- controllers/templateresourcedef_utils.go | 90 +++--------------- controllers/templateresourcedef_utils_test.go | 93 +++++++++++++++++-- 4 files changed, 106 insertions(+), 93 deletions(-) diff --git a/controllers/clustersummary_controller.go b/controllers/clustersummary_controller.go index 58b12c34..4e89b58a 100644 --- a/controllers/clustersummary_controller.go +++ b/controllers/clustersummary_controller.go @@ -310,7 +310,7 @@ func (r *ClusterSummaryReconciler) reconcileDelete( r.cleanMaps(clusterSummaryScope) manager := getManager() - manager.stopStaleWatchForTemplateResourceRef(clusterSummaryScope.ClusterSummary, true) + manager.stopStaleWatchForTemplateResourceRef(ctx, clusterSummaryScope.ClusterSummary, true) logger.V(logs.LogInfo).Info("Reconcile delete success") @@ -1342,7 +1342,7 @@ func (r *ClusterSummaryReconciler) startWatcherForTemplateResourceRefs(ctx conte } } - manager.stopStaleWatchForTemplateResourceRef(clusterSummary, false) + manager.stopStaleWatchForTemplateResourceRef(ctx, clusterSummary, false) return nil } diff --git a/controllers/clustersummary_watchers.go b/controllers/clustersummary_watchers.go index 3a4b5fd0..92d234d6 100644 --- a/controllers/clustersummary_watchers.go +++ b/controllers/clustersummary_watchers.go @@ -155,12 +155,12 @@ func (m *manager) startWatcherForTemplateResourceRef(ctx context.Context, gvk sc var err error // If namespace is not defined, default to cluster namespace - resource.Namespace, err = getTemplateResourceNamespace(clusterSummary, ref) + resource.Namespace, err = getTemplateResourceNamespace(ctx, clusterSummary, ref) if err != nil { return err } - resource.Name, err = getTemplateResourceName(clusterSummary, ref) + resource.Name, err = getTemplateResourceName(ctx, clusterSummary, ref) if err != nil { return err } @@ -257,8 +257,8 @@ func (m *manager) stopStaleWatchForMgmtResource(currentResources map[corev1.Obje // It then stops any watchers that were previously set up to deliver notifications about those specific // resources to ClusterSummary. // Resources that are still included in the currentResources map will continue to be watched. -func (m *manager) stopStaleWatchForTemplateResourceRef(clusterSummary *configv1beta1.ClusterSummary, - removeAll bool) { +func (m *manager) stopStaleWatchForTemplateResourceRef(ctx context.Context, + clusterSummary *configv1beta1.ClusterSummary, removeAll bool) { consumer := &corev1.ObjectReference{ APIVersion: configv1beta1.GroupVersion.Group, @@ -273,12 +273,12 @@ func (m *manager) stopStaleWatchForTemplateResourceRef(clusterSummary *configv1b for i := range clusterSummary.Spec.ClusterProfileSpec.TemplateResourceRefs { resource := &clusterSummary.Spec.ClusterProfileSpec.TemplateResourceRefs[i].Resource var err error - resource.Namespace, err = getTemplateResourceNamespace(clusterSummary, + resource.Namespace, err = getTemplateResourceNamespace(ctx, clusterSummary, &clusterSummary.Spec.ClusterProfileSpec.TemplateResourceRefs[i]) if err != nil { continue } - resource.Name, _ = getTemplateResourceName(clusterSummary, + resource.Name, _ = getTemplateResourceName(ctx, clusterSummary, &clusterSummary.Spec.ClusterProfileSpec.TemplateResourceRefs[i]) currentResources[*resource] = true diff --git a/controllers/templateresourcedef_utils.go b/controllers/templateresourcedef_utils.go index 637ad738..f9634156 100644 --- a/controllers/templateresourcedef_utils.go +++ b/controllers/templateresourcedef_utils.go @@ -17,100 +17,34 @@ limitations under the License. package controllers import ( - "bytes" "context" - "fmt" - "text/template" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" - "github.com/projectsveltos/libsveltos/lib/funcmap" "github.com/projectsveltos/libsveltos/lib/k8s_utils" + libsveltostemplate "github.com/projectsveltos/libsveltos/lib/template" ) // The TemplateResource namespace can be specified or it will inherit the cluster namespace -func getTemplateResourceNamespace(clusterSummary *configv1beta1.ClusterSummary, +func getTemplateResourceNamespace(ctx context.Context, clusterSummary *configv1beta1.ClusterSummary, ref *configv1beta1.TemplateResourceRef) (string, error) { - namespace := ref.Resource.Namespace - if namespace == "" { - // Use cluster namespace - return clusterSummary.Spec.ClusterNamespace, nil - } - - // Accept namespaces that are templates - templateName := getTemplateName(clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, - string(clusterSummary.Spec.ClusterType)) - tmpl, err := template.New(templateName).Option("missingkey=error").Funcs( - funcmap.SveltosFuncMap(funcmap.HasTextTemplateAnnotation(clusterSummary.Annotations))).Parse(ref.Resource.Namespace) - if err != nil { - return "", err - } - - var buffer bytes.Buffer - - // Cluster namespace and name can be used to instantiate the name of the resource that - // needs to be fetched from the management cluster. Defined an unstructured with namespace and name set - u := &unstructured.Unstructured{} - u.SetNamespace(clusterSummary.Spec.ClusterNamespace) - u.SetName(clusterSummary.Spec.ClusterName) - u.SetKind(string(clusterSummary.Spec.ClusterType)) - - if err := tmpl.Execute(&buffer, - struct { - Cluster map[string]interface{} - // deprecated. This used to be original format which was different than rest of templating - ClusterNamespace, ClusterName string - }{ - Cluster: u.UnstructuredContent(), - ClusterNamespace: clusterSummary.Spec.ClusterNamespace, - ClusterName: clusterSummary.Spec.ClusterName}); err != nil { - return "", fmt.Errorf("error executing template: %w", err) - } - return buffer.String(), nil + return libsveltostemplate.GetReferenceResourceNamespace(ctx, getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, ref.Resource.Namespace, + clusterSummary.Spec.ClusterType) } // Resources referenced in the management cluster can have their name expressed in function -// of cluster information: -// clusterNamespace => .Cluster.metadata.namespace -// clusterName => .Cluster.metadata.name -// clusterType => .Cluster.kind -func getTemplateResourceName(clusterSummary *configv1beta1.ClusterSummary, +// of cluster field +func getTemplateResourceName(ctx context.Context, clusterSummary *configv1beta1.ClusterSummary, ref *configv1beta1.TemplateResourceRef) (string, error) { - // Accept name that are templates - templateName := getTemplateName(clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, - string(clusterSummary.Spec.ClusterType)) - tmpl, err := template.New(templateName).Option("missingkey=error").Funcs( - funcmap.SveltosFuncMap(funcmap.HasTextTemplateAnnotation(clusterSummary.Annotations))).Parse(ref.Resource.Name) - if err != nil { - return "", err - } - - var buffer bytes.Buffer - - // Cluster namespace and name can be used to instantiate the name of the resource that - // needs to be fetched from the management cluster. Defined an unstructured with namespace and name set - u := &unstructured.Unstructured{} - u.SetNamespace(clusterSummary.Spec.ClusterNamespace) - u.SetName(clusterSummary.Spec.ClusterName) - u.SetKind(string(clusterSummary.Spec.ClusterType)) - - if err := tmpl.Execute(&buffer, - struct { - Cluster map[string]interface{} - // deprecated. This used to be original format which was different than rest of templating - ClusterNamespace, ClusterName string - }{ - Cluster: u.UnstructuredContent(), - ClusterNamespace: clusterSummary.Spec.ClusterNamespace, - ClusterName: clusterSummary.Spec.ClusterName}); err != nil { - return "", fmt.Errorf("error executing template: %w", err) - } - return buffer.String(), nil + return libsveltostemplate.GetReferenceResourceName(ctx, getManagementClusterClient(), + clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, ref.Resource.Name, + clusterSummary.Spec.ClusterType) } // collectTemplateResourceRefs collects clusterSummary.Spec.ClusterProfileSpec.TemplateResourceRefs @@ -128,11 +62,11 @@ func collectTemplateResourceRefs(ctx context.Context, clusterSummary *configv1be for i := range clusterSummary.Spec.ClusterProfileSpec.TemplateResourceRefs { ref := clusterSummary.Spec.ClusterProfileSpec.TemplateResourceRefs[i] var err error - ref.Resource.Namespace, err = getTemplateResourceNamespace(clusterSummary, &ref) + ref.Resource.Namespace, err = getTemplateResourceNamespace(ctx, clusterSummary, &ref) if err != nil { return nil, err } - ref.Resource.Name, err = getTemplateResourceName(clusterSummary, &ref) + ref.Resource.Name, err = getTemplateResourceName(ctx, clusterSummary, &ref) if err != nil { return nil, err } diff --git a/controllers/templateresourcedef_utils_test.go b/controllers/templateresourcedef_utils_test.go index 47c546b1..0cac3bfd 100644 --- a/controllers/templateresourcedef_utils_test.go +++ b/controllers/templateresourcedef_utils_test.go @@ -17,6 +17,9 @@ limitations under the License. package controllers_test import ( + "context" + "fmt" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -32,6 +35,8 @@ import ( var _ = Describe("TemplateResourceDef utils ", func() { var cluster *clusterv1.Cluster var namespace string + var labelKey, labelValue string + var annotationKey, annotationValue string BeforeEach(func() { var err error @@ -40,15 +45,34 @@ var _ = Describe("TemplateResourceDef utils ", func() { namespace = randomString() + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + + Expect(testEnv.Create(context.TODO(), ns)).To(Succeed()) + Expect(waitForObject(context.TODO(), testEnv.Client, ns)).To(Succeed()) + + labelKey = randomString() + labelValue = randomString() + annotationKey = randomString() + annotationValue = randomString() cluster = &clusterv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: upstreamClusterNamePrefix + randomString(), Namespace: namespace, Labels: map[string]string{ - randomString(): randomString(), + labelKey: labelValue, + }, + Annotations: map[string]string{ + annotationKey: annotationValue, }, }, } + + Expect(testEnv.Create(context.TODO(), cluster)).To(Succeed()) + Expect(waitForObject(context.TODO(), testEnv.Client, cluster)).To(Succeed()) }) It("GetTemplateResourceName returns the correct name (uses ClusterNamespace and ClusterName)", func() { @@ -70,11 +94,37 @@ var _ = Describe("TemplateResourceDef utils ", func() { }, } - value, err := controllers.GetTemplateResourceName(clusterSummary, ref) + value, err := controllers.GetTemplateResourceName(context.TODO(), clusterSummary, ref) Expect(err).To(BeNil()) Expect(value).To(Equal(cluster.Namespace + "-" + cluster.Name)) }) + It("GetTemplateResourceName returns the correct name (uses cluster label)", func() { + name := fmt.Sprintf("{{ index .Cluster.metadata.labels %q }}-{{ index .Cluster.metadata.annotations %q }}", + labelKey, annotationKey) + ref := &configv1beta1.TemplateResourceRef{ + Resource: corev1.ObjectReference{ + Name: name, + }, + Identifier: randomString(), + } + + clusterSummary := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: configv1beta1.ClusterSummarySpec{ + ClusterNamespace: cluster.Namespace, + ClusterName: cluster.Name, + ClusterType: libsveltosv1beta1.ClusterTypeCapi, + }, + } + + value, err := controllers.GetTemplateResourceName(context.TODO(), clusterSummary, ref) + Expect(err).To(BeNil()) + Expect(value).To(Equal(fmt.Sprintf("%s-%s", labelValue, annotationValue))) + }) + It("GetTemplateResourceNamespace returns the correct namespace (uses Cluster)", func() { ref := &configv1beta1.TemplateResourceRef{ Resource: corev1.ObjectReference{ @@ -94,7 +144,7 @@ var _ = Describe("TemplateResourceDef utils ", func() { }, } - value, err := controllers.GetTemplateResourceName(clusterSummary, ref) + value, err := controllers.GetTemplateResourceName(context.TODO(), clusterSummary, ref) Expect(err).To(BeNil()) Expect(value).To(Equal(cluster.Namespace + "-" + cluster.Name)) }) @@ -118,12 +168,12 @@ var _ = Describe("TemplateResourceDef utils ", func() { }, } - value, err := controllers.GetTemplateResourceNamespace(clusterSummary, ref) + value, err := controllers.GetTemplateResourceNamespace(context.TODO(), clusterSummary, ref) Expect(err).To(BeNil()) Expect(value).To(Equal(cluster.Namespace)) ref.Resource.Namespace = randomString() - value, err = controllers.GetTemplateResourceNamespace(clusterSummary, ref) + value, err = controllers.GetTemplateResourceNamespace(context.TODO(), clusterSummary, ref) Expect(err).To(BeNil()) Expect(value).To(Equal(ref.Resource.Namespace)) }) @@ -148,14 +198,43 @@ var _ = Describe("TemplateResourceDef utils ", func() { }, } - value, err := controllers.GetTemplateResourceNamespace(clusterSummary, ref) + value, err := controllers.GetTemplateResourceNamespace(context.TODO(), clusterSummary, ref) Expect(err).To(BeNil()) Expect(value).To(Equal(cluster.Namespace + "-foo")) ref.Resource.Namespace = randomString() - value, err = controllers.GetTemplateResourceNamespace(clusterSummary, ref) + value, err = controllers.GetTemplateResourceNamespace(context.TODO(), clusterSummary, ref) Expect(err).To(BeNil()) Expect(value).To(Equal(ref.Resource.Namespace)) }) + It("GetTemplateResourceNamespace returns the correct namespace (template version with labels)", func() { + ref := &configv1beta1.TemplateResourceRef{ + Resource: corev1.ObjectReference{ + Name: randomString(), + Namespace: fmt.Sprintf("{{ index .Cluster.metadata.labels %q }}", labelKey), + }, + Identifier: randomString(), + } + + clusterSummary := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: configv1beta1.ClusterSummarySpec{ + ClusterNamespace: cluster.Namespace, + ClusterName: cluster.Name, + ClusterType: libsveltosv1beta1.ClusterTypeCapi, + }, + } + + value, err := controllers.GetTemplateResourceNamespace(context.TODO(), clusterSummary, ref) + Expect(err).To(BeNil()) + Expect(value).To(Equal(labelValue)) + + ref.Resource.Namespace = randomString() + value, err = controllers.GetTemplateResourceNamespace(context.TODO(), clusterSummary, ref) + Expect(err).To(BeNil()) + Expect(value).To(Equal(ref.Resource.Namespace)) + }) })