diff --git a/pkg/controller/options.go b/pkg/controller/options.go index 179a5412f..4be8a73b4 100644 --- a/pkg/controller/options.go +++ b/pkg/controller/options.go @@ -77,6 +77,11 @@ type Options struct { // EventFilterFunctions used to filter events emitted by the controllers. EventFilterFunctions []event.FilterFn + + // Groups is the list of API groups managed by this provider. + // If specified, the CRD gate will only watch CRDs belonging to these groups, + // reducing unnecessary reconciles from unrelated CRDs in the cluster. + Groups []string } // ForControllerRuntime extracts options for controller-runtime. diff --git a/pkg/reconciler/customresourcesgate/reconciler_test.go b/pkg/reconciler/customresourcesgate/reconciler_test.go index 2ab6789ad..498d322d6 100644 --- a/pkg/reconciler/customresourcesgate/reconciler_test.go +++ b/pkg/reconciler/customresourcesgate/reconciler_test.go @@ -23,10 +23,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" "github.com/crossplane/crossplane-runtime/v2/pkg/logging" "github.com/crossplane/crossplane-runtime/v2/pkg/test" @@ -560,3 +563,85 @@ func TestReconcile(t *testing.T) { }) } } + +func TestGroupPredicate(t *testing.T) { + type args struct { + groups []string + obj client.Object + } + type want struct { + match bool + } + + cases := map[string]struct { + reason string + args args + want want + }{ + "CRDGroupPresent": { + reason: "CRD is present in the specified groups, predicate should return true", + args: args{ + groups: []string{"example.com"}, + obj: &apiextensionsv1.CustomResourceDefinition{ + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + }, + }, + }, + want: want{ + match: true, + }, + }, + "CRDGroupAbsent": { + reason: "CRD is not present in the specified groups, predicate should return false", + args: args{ + groups: []string{"example.com"}, + obj: &apiextensionsv1.CustomResourceDefinition{ + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "other.com", + }, + }, + }, + want: want{ + match: false, + }, + }, + "EmptyGroups": { + reason: "No groups specified, predicate should return false for any CRD", + args: args{ + groups: []string{}, + obj: &apiextensionsv1.CustomResourceDefinition{ + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + }, + }, + }, + want: want{ + match: false, + }, + }, + "NonCRDObject": { + reason: "Object is not a CRD, predicate should return false", + args: args{ + groups: []string{"example.com"}, + obj: &corev1.ConfigMap{}, + }, + want: want{ + match: false, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := GroupPredicate(tc.args.groups...) + ev := event.GenericEvent{ + Object: tc.args.obj, + } + match := got.Generic(ev) + if match != tc.want.match { + t.Errorf("\n%s\nGroupPredicate(...): -want match, +got match:\n%s", tc.reason, cmp.Diff(tc.want.match, match)) + } + }) + } +} diff --git a/pkg/reconciler/customresourcesgate/setup.go b/pkg/reconciler/customresourcesgate/setup.go index 1016968b9..36f403286 100644 --- a/pkg/reconciler/customresourcesgate/setup.go +++ b/pkg/reconciler/customresourcesgate/setup.go @@ -22,6 +22,8 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/crossplane/crossplane-runtime/v2/pkg/controller" @@ -38,9 +40,29 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { log: o.Logger, gate: o.Gate, } - - return ctrl.NewControllerManagedBy(mgr). + b := ctrl.NewControllerManagedBy(mgr). For(&apiextensionsv1.CustomResourceDefinition{}). - Named("crd-gate"). - Complete(reconcile.AsReconciler[*apiextensionsv1.CustomResourceDefinition](mgr.GetClient(), r)) + Named("crd-gate") + + if len(o.Groups) > 0 { + b = b.WithEventFilter(GroupPredicate(o.Groups...)) + } + + return b.Complete(reconcile.AsReconciler[*apiextensionsv1.CustomResourceDefinition](mgr.GetClient(), r)) +} + +// GroupPredicate returns a predicate that filters CustomResourceDefinitions by group. +func GroupPredicate(groups ...string) predicate.Predicate { + groupSet := make(map[string]struct{}) + for _, g := range groups { + groupSet[g] = struct{}{} + } + return predicate.NewPredicateFuncs(func(obj client.Object) bool { + crd, ok := obj.(*apiextensionsv1.CustomResourceDefinition) + if !ok { + return false + } + _, ok = groupSet[crd.Spec.Group] + return ok + }) }