From 09af5a009deffdf521fe0e49852200dbdb8e8224 Mon Sep 17 00:00:00 2001 From: Giulio Cardillo Date: Tue, 21 Apr 2026 18:14:24 +0200 Subject: [PATCH 1/3] Drop analytics.enabled config parameter --- cmd/main.go | 21 +------- cmd/main_test.go | 74 ---------------------------- internal/controller/defaults.go | 1 - internal/controller/defaults_test.go | 7 --- 4 files changed, 1 insertion(+), 102 deletions(-) delete mode 100644 cmd/main_test.go diff --git a/cmd/main.go b/cmd/main.go index 1ac5f8978..bf4a8c6c4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -120,7 +120,7 @@ func main() { setupLog.Error(err, "unable to add console plugin runnable") } - analyticsEnabled := areAnalyticsEnabled(mgr.GetAPIReader()) + analyticsEnabled := strings.ToLower(os.Getenv("ANALYTICS")) != "false" setupLog.Info("analytics enabled", "enabled", analyticsEnabled) if err = (&controllers.PatternReconciler{ Client: mgr.GetClient(), @@ -208,22 +208,3 @@ func registerComponentOrExit(mgr manager.Manager, f func(*k8sruntime.Scheme) err } setupLog.Info(fmt.Sprintf("Component registered: %v", reflect.ValueOf(f))) } - -// areAnalyticsEnabled determines whether analytics are enabled. -// Precedence: Operator ConfigMap key "analytics.enabled" (true/false) > ENV ANALYTICS (false means disabled) -func areAnalyticsEnabled(reader crclient.Reader) bool { - enabled := strings.ToLower(os.Getenv("ANALYTICS")) != "false" - - var cm corev1.ConfigMap - err := reader.Get(context.Background(), crclient.ObjectKey{Namespace: controllers.DetectOperatorNamespace(), Name: controllers.OperatorConfigMap}, &cm) - if err != nil { - setupLog.Error(err, "error reading operator configmap for analytics setting") - return enabled - } - - if v, ok := cm.Data["analytics.enabled"]; ok { - return strings.EqualFold(v, "true") - } - - return enabled -} diff --git a/cmd/main_test.go b/cmd/main_test.go deleted file mode 100644 index d88937be4..000000000 --- a/cmd/main_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "os" - "testing" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - crclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - controllers "github.com/hybrid-cloud-patterns/patterns-operator/internal/controller" -) - -func newFakeReader(objs ...crclient.Object) crclient.Reader { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - builder := fake.NewClientBuilder().WithScheme(scheme) - if len(objs) > 0 { - builder = builder.WithObjects(objs...) - } - return builder.Build() -} - -func newOperatorConfigMap(analyticsEnabled string) *corev1.ConfigMap { - return &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: controllers.DetectOperatorNamespace(), - Name: controllers.OperatorConfigMap, - }, - Data: map[string]string{ - "analytics.enabled": analyticsEnabled, - }, - } -} - -func TestAreAnalyticsEnabledWithReader_ConfigMapTrue(t *testing.T) { - cm := newOperatorConfigMap("true") - reader := newFakeReader(cm) - - if got := areAnalyticsEnabled(reader); got != true { - t.Fatalf("expected true when configmap sets analytics.enabled=true, got %v", got) - } -} - -func TestAreAnalyticsEnabledWithReader_ConfigMapFalse(t *testing.T) { - cm := newOperatorConfigMap("false") - reader := newFakeReader(cm) - - if got := areAnalyticsEnabled(reader); got != false { - t.Fatalf("expected false when configmap sets analytics.enabled=false, got %v", got) - } -} - -func TestAreAnalyticsEnabledWithReader_NoConfigMap_EnvFalseDisables(t *testing.T) { - _ = os.Setenv("ANALYTICS", "false") - t.Cleanup(func() { _ = os.Unsetenv("ANALYTICS") }) - - reader := newFakeReader() - if got := areAnalyticsEnabled(reader); got != false { - t.Fatalf("expected false when no configmap and ANALYTICS=false, got %v", got) - } -} - -func TestAreAnalyticsEnabledWithReader_NoConfigMap_EnvTrueEnables(t *testing.T) { - _ = os.Setenv("ANALYTICS", "true") - t.Cleanup(func() { _ = os.Unsetenv("ANALYTICS") }) - - reader := newFakeReader() - if got := areAnalyticsEnabled(reader); got != true { - t.Fatalf("expected true when no configmap and ANALYTICS=true, got %v", got) - } -} diff --git a/internal/controller/defaults.go b/internal/controller/defaults.go index b10f079da..83de3fe4c 100644 --- a/internal/controller/defaults.go +++ b/internal/controller/defaults.go @@ -97,7 +97,6 @@ var DefaultPatternsOperatorConfig = map[string]string{ "gitea.chartName": GiteaChartName, "gitea.helmRepoUrl": GiteaHelmRepoUrl, "gitea.chartVersion": GiteaDefaultChartVersion, - "analytics.enabled": "true", "catalog.image": "", } diff --git a/internal/controller/defaults_test.go b/internal/controller/defaults_test.go index d55ce8d42..7e6692d05 100644 --- a/internal/controller/defaults_test.go +++ b/internal/controller/defaults_test.go @@ -55,11 +55,6 @@ var _ = Describe("PatternsOperatorConfig getValueWithDefault", func() { config := PatternsOperatorConfig{} Expect(config.getValueWithDefault("gitea.chartVersion")).To(Equal(GiteaDefaultChartVersion)) }) - - It("should return the default value for analytics.enabled", func() { - config := PatternsOperatorConfig{} - Expect(config.getValueWithDefault("analytics.enabled")).To(Equal("true")) - }) }) Context("when the key does not exist in config or defaults", func() { @@ -96,7 +91,6 @@ var _ = Describe("DefaultPatternsOperatorConfig", func() { "gitea.chartName", "gitea.helmRepoUrl", "gitea.chartVersion", - "analytics.enabled", } for _, key := range expectedKeys { Expect(DefaultPatternsOperatorConfig).To(HaveKey(key)) @@ -107,6 +101,5 @@ var _ = Describe("DefaultPatternsOperatorConfig", func() { Expect(DefaultPatternsOperatorConfig["gitops.catalogSource"]).To(Equal("redhat-operators")) Expect(DefaultPatternsOperatorConfig["gitops.sourceNamespace"]).To(Equal("openshift-marketplace")) Expect(DefaultPatternsOperatorConfig["gitops.installApprovalPlan"]).To(Equal("Automatic")) - Expect(DefaultPatternsOperatorConfig["analytics.enabled"]).To(Equal("true")) }) }) From d610822ed853426507170aa9274084af328929b8 Mon Sep 17 00:00:00 2001 From: Giulio Cardillo Date: Tue, 21 Apr 2026 18:27:38 +0200 Subject: [PATCH 2/3] Convert Patterns Operator config functions to PatternReconciler methods --- cmd/main.go | 49 ----------------------- internal/controller/pattern_controller.go | 47 +++++++++++++++++++--- internal/controller/utils.go | 15 ------- 3 files changed, 41 insertions(+), 70 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index bf4a8c6c4..d5b69740e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,12 +29,10 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/apimachinery/pkg/api/errors" k8sruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" - crclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -46,9 +44,6 @@ import ( "github.com/hybrid-cloud-patterns/patterns-operator/version" consolev1 "github.com/openshift/api/console/v1" operatorv1 "github.com/openshift/api/operator/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" //+kubebuilder:scaffold:imports ) @@ -85,12 +80,6 @@ func main() { setupLog.Info("detected operator namespace", "namespace", controllers.DetectOperatorNamespace()) - // Create initial config map for gitops - err := createPatternsOperatorConfigMap() - if err != nil { - setupLog.Error(err, "unable to create config map") - } - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, HealthProbeBindAddress: probeAddr, @@ -162,44 +151,6 @@ func printVersion() { setupLog.Info(fmt.Sprintf("Build Date: %s", version.BuildDate)) } -// Creates the patterns operator configmap -// This will include configuration parameters that -// will allow operator configuration -func createPatternsOperatorConfigMap() error { - config, err := ctrl.GetConfig() - if err != nil { - return fmt.Errorf("failed to get config: %s", err) - } - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return fmt.Errorf("failed to call NewForConfig: %s", err) - } - - configMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: controllers.OperatorConfigMap, - Namespace: controllers.DetectOperatorNamespace(), - }, - } - - _, err = clientset.CoreV1().ConfigMaps(controllers.DetectOperatorNamespace()).Get(context.Background(), controllers.OperatorConfigMap, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - // if the configmap does not exist we create an empty one - _, err = clientset.CoreV1().ConfigMaps(controllers.DetectOperatorNamespace()).Create(context.Background(), &configMap, metav1.CreateOptions{}) - if err != nil { - return err - } - } else { - // if we had an error that is not IsNotFound we need to return it - return err - } - return nil -} - func registerComponentOrExit(mgr manager.Manager, f func(*k8sruntime.Scheme) error) { // Setup Scheme for all resources if err := f(mgr.GetScheme()); err != nil { diff --git a/internal/controller/pattern_controller.go b/internal/controller/pattern_controller.go index 8ba0f993b..901a930df 100644 --- a/internal/controller/pattern_controller.go +++ b/internal/controller/pattern_controller.go @@ -34,6 +34,7 @@ import ( "github.com/go-logr/logr" "github.com/hybrid-cloud-patterns/patterns-operator/internal/controller/console" + corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -149,14 +150,19 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return reconcile.Result{}, err } + // Set Patterns Operator config to defaults patternsOperatorConfig := DefaultPatternsOperatorConfig - configCM, err := GetPatternsOperatorConfigMap() - // If we hit an error that is not related to the configmap not existing bubble it up - if err != nil && !kerrors.IsNotFound(err) { + // We try to get the configuration ConfigMap. The method getPatternsOperatorConfigMap returns nil, nil if the ConfigMap doesn't exist + configCM, err := r.getPatternsOperatorConfigMap() + if err != nil { return r.actionPerformed(instance, "failed to get the configuration ConfigMap", err) } - if configCM != nil { + if configCM == nil { // If the ConfigMap doesn't exist, we create it + if err = r.createPatternsOperatorConfigMap(instance); err != nil { + return r.actionPerformed(instance, "failed to create the configuration ConfigMap", err) + } + } else { // If the ConfigMap exists, we get the configuration from Data patternsOperatorConfig = configCM.Data } @@ -398,8 +404,8 @@ func (r *PatternReconciler) reconcileGitOpsSubscription(qualifiedInstance *api.P if err := controllerutil.RemoveOwnerReference(qualifiedInstance, currentSub, r.Scheme); err == nil { changed = true } - operatorConfigMap, cmErr := GetPatternsOperatorConfigMap() - if cmErr == nil { + operatorConfigMap, cmErr := r.getPatternsOperatorConfigMap() + if cmErr == nil && operatorConfigMap != nil { if err := controllerutil.RemoveOwnerReference(operatorConfigMap, currentSub, r.Scheme); err == nil { changed = true } @@ -1325,6 +1331,35 @@ func (r *PatternReconciler) getLocalGit(p *api.Pattern) (string, error) { return "", nil } +func (r *PatternReconciler) createPatternsOperatorConfigMap(p *api.Pattern) error { + configMap := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: OperatorConfigMap, + Namespace: DetectOperatorNamespace(), + }, + } + if _, err := r.fullClient.CoreV1().ConfigMaps(DetectOperatorNamespace()).Create(context.Background(), &configMap, metav1.CreateOptions{}); err != nil { + return err + } + return nil +} + +func (r *PatternReconciler) getPatternsOperatorConfigMap() (*corev1.ConfigMap, error) { + cm, err := r.fullClient.CoreV1().ConfigMaps(DetectOperatorNamespace()).Get(context.Background(), OperatorConfigMap, metav1.GetOptions{}) + if err != nil { + if kerrors.IsNotFound(err) { + return nil, nil + } else { + return nil, err + } + } + return cm, nil +} + func DropLocalGitPaths() error { // If there is a completely new local folder, let's remove the old one // User changed the target repo diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 6aef79a68..34c0fad71 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -41,7 +41,6 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - ctrl "sigs.k8s.io/controller-runtime" configv1 "github.com/openshift/api/config/v1" ) @@ -449,17 +448,3 @@ func IntOrZero(secret map[string][]byte, key string) (int64, error) { return strconv.ParseInt(string(val), 10, 64) } - -// Gets the configmap for the Patterns Operator. (Used as an owner reference for the operator itself.) -func GetPatternsOperatorConfigMap() (*corev1.ConfigMap, error) { - config, err := ctrl.GetConfig() - if err != nil { - return nil, fmt.Errorf("failed to get config: %s", err) - } - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf("failed to call NewForConfig: %s", err) - } - - return clientset.CoreV1().ConfigMaps(DetectOperatorNamespace()).Get(context.Background(), OperatorConfigMap, metav1.GetOptions{}) -} From f7655a7188181ae1be64e7d4b2735807c0e55a42 Mon Sep 17 00:00:00 2001 From: Giulio Cardillo Date: Tue, 21 Apr 2026 18:28:35 +0200 Subject: [PATCH 3/3] Set Pattern Operator config ConfigMap ownership to Pattern CR --- internal/controller/pattern_controller.go | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/internal/controller/pattern_controller.go b/internal/controller/pattern_controller.go index 901a930df..867d18f3a 100644 --- a/internal/controller/pattern_controller.go +++ b/internal/controller/pattern_controller.go @@ -162,7 +162,10 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct if err = r.createPatternsOperatorConfigMap(instance); err != nil { return r.actionPerformed(instance, "failed to create the configuration ConfigMap", err) } - } else { // If the ConfigMap exists, we get the configuration from Data + } else { // If the ConfigMap exists, we set the ownership and get the configuration from Data + if err = r.setPatternsOperatorConfigMapOwnership(configCM, instance); err != nil { + return r.actionPerformed(instance, "failed to set ownership of configuration ConfigMap", err) + } patternsOperatorConfig = configCM.Data } @@ -913,6 +916,7 @@ func (r *PatternReconciler) SetupWithManager(mgr ctrl.Manager) error { var ctrlErr error r.ctrl, ctrlErr = ctrl.NewControllerManagedBy(mgr). For(&api.Pattern{}). + Owns(&corev1.ConfigMap{}). Build(r) return ctrlErr } @@ -1342,6 +1346,9 @@ func (r *PatternReconciler) createPatternsOperatorConfigMap(p *api.Pattern) erro Namespace: DetectOperatorNamespace(), }, } + if err := controllerutil.SetControllerReference(p, &configMap, r.Scheme); err != nil { + return err + } if _, err := r.fullClient.CoreV1().ConfigMaps(DetectOperatorNamespace()).Create(context.Background(), &configMap, metav1.CreateOptions{}); err != nil { return err } @@ -1360,6 +1367,21 @@ func (r *PatternReconciler) getPatternsOperatorConfigMap() (*corev1.ConfigMap, e return cm, nil } +func (r *PatternReconciler) setPatternsOperatorConfigMapOwnership(cm *corev1.ConfigMap, p *api.Pattern) error { + controllerRef := metav1.GetControllerOf(cm) + if controllerRef != nil && controllerRef.UID == p.GetUID() && controllerRef.Kind == p.Kind && controllerRef.APIVersion == p.APIVersion { + return nil + } + if err := controllerutil.SetControllerReference(p, cm, r.Scheme); err != nil { + return err + } + _, err := r.fullClient.CoreV1().ConfigMaps(DetectOperatorNamespace()).Update(context.Background(), cm, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil +} + func DropLocalGitPaths() error { // If there is a completely new local folder, let's remove the old one // User changed the target repo