diff --git a/pkg/operator/v1helpers/informers.go b/pkg/operator/v1helpers/informers.go index 4d38ef05dc..d084119c22 100644 --- a/pkg/operator/v1helpers/informers.go +++ b/pkg/operator/v1helpers/informers.go @@ -6,6 +6,7 @@ import ( "time" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" @@ -33,20 +34,37 @@ type KubeInformersForNamespaces interface { var _ KubeInformersForNamespaces = kubeInformersForNamespaces{} func NewKubeInformersForNamespacesWithResyncPeriod(kubeClient kubernetes.Interface, resyncInterval time.Duration, namespaces ...string) KubeInformersForNamespaces { + return NewKubeInformersForNamespacesWithOptions(kubeClient, resyncInterval, namespaces) +} + +func NewKubeInformersForNamespaces(kubeClient kubernetes.Interface, namespaces ...string) KubeInformersForNamespaces { + return NewKubeInformersForNamespacesWithOptions(kubeClient, 10*time.Minute, namespaces) +} + +func NewKubeInformersForNamespacesWithOptions(kubeClient kubernetes.Interface, resyncInterval time.Duration, namespaces []string, opts ...informers.SharedInformerOption) KubeInformersForNamespaces { ret := kubeInformersForNamespaces{} for _, namespace := range namespaces { - if len(namespace) == 0 { - ret[""] = informers.NewSharedInformerFactory(kubeClient, resyncInterval) - continue + factoryOpts := make([]informers.SharedInformerOption, 0, len(opts)+1) + factoryOpts = append(factoryOpts, opts...) + if len(namespace) != 0 { + factoryOpts = append(factoryOpts, informers.WithNamespace(namespace)) } - ret[namespace] = informers.NewSharedInformerFactoryWithOptions(kubeClient, resyncInterval, informers.WithNamespace(namespace)) + ret[namespace] = informers.NewSharedInformerFactoryWithOptions(kubeClient, resyncInterval, factoryOpts...) } - return ret } -func NewKubeInformersForNamespaces(kubeClient kubernetes.Interface, namespaces ...string) KubeInformersForNamespaces { - return NewKubeInformersForNamespacesWithResyncPeriod(kubeClient, 10*time.Minute, namespaces...) +// StripManagedFieldsTransform is a cache.TransformFunc that removes +// ManagedFields from objects before they are stored in the informer cache. +func StripManagedFieldsTransform(obj interface{}) (interface{}, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return obj, nil + } + if accessor.GetManagedFields() != nil { + accessor.SetManagedFields(nil) + } + return obj, nil } type kubeInformersForNamespaces map[string]informers.SharedInformerFactory diff --git a/pkg/operator/v1helpers/informers_test.go b/pkg/operator/v1helpers/informers_test.go new file mode 100644 index 0000000000..fd5e3aecc3 --- /dev/null +++ b/pkg/operator/v1helpers/informers_test.go @@ -0,0 +1,104 @@ +package v1helpers + +import ( + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakekube "k8s.io/client-go/kubernetes/fake" +) + +func TestNewKubeInformersForNamespacesWithOptions(t *testing.T) { + kubeClient := fakekube.NewSimpleClientset() + + testCases := []struct { + name string + namespaces []string + }{ + { + name: "single namespace", + namespaces: []string{"openshift-config"}, + }, + { + name: "multiple namespaces", + namespaces: []string{"openshift-config", "openshift-config-managed"}, + }, + { + name: "cluster-wide", + namespaces: []string{""}, + }, + { + name: "cluster-wide and namespaced", + namespaces: []string{"", "openshift-config"}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ki := NewKubeInformersForNamespacesWithOptions(kubeClient, 10*time.Minute, tc.namespaces) + + for _, ns := range tc.namespaces { + if ki.InformersFor(ns) == nil { + t.Errorf("expected informers for namespace %q", ns) + } + } + if ki.InformersFor("not-registered") != nil { + t.Error("expected no informers for unregistered namespace") + } + }) + } +} + +func TestStripManagedFieldsTransform(t *testing.T) { + testCases := []struct { + name string + obj interface{} + expectManagedNil bool + expectPassThrough bool + }{ + { + name: "strips managed fields from object that has them", + obj: &metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: "kubectl", Operation: metav1.ManagedFieldsOperationApply}, + }, + }, + expectManagedNil: true, + }, + { + name: "passes through object without managed fields", + obj: &metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + expectManagedNil: true, + }, + { + name: "passes through non-Object types without error", + obj: "not-an-object", + expectPassThrough: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := StripManagedFieldsTransform(tc.obj) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tc.expectPassThrough { + if result != tc.obj { + t.Fatal("expected pass-through for non-Object type") + } + return + } + accessor, ok := result.(metav1.Object) + if !ok { + t.Fatal("expected result to implement metav1.Object") + } + if tc.expectManagedNil && accessor.GetManagedFields() != nil { + t.Fatal("expected ManagedFields to be nil") + } + }) + } +}