Skip to content

Commit 4c1d0e4

Browse files
mszostokDirectXMan12
authored andcommitted
Add per-watch predicates for Owns/For/Watches
This allows per-watch predicates for For, Owns, and Watches in the builder. These are in addition to the global predicate set, which apply to all watches.
1 parent b97ebda commit 4c1d0e4

File tree

2 files changed

+106
-23
lines changed

2 files changed

+106
-23
lines changed

pkg/builder/controller.go

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ var getGvk = apiutil.GVKForObject
3737

3838
// Builder builds a Controller.
3939
type Builder struct {
40-
apiType runtime.Object
41-
mgr manager.Manager
42-
predicates []predicate.Predicate
43-
managedObjects []runtime.Object
44-
watchRequest []watchRequest
45-
config *rest.Config
46-
ctrl controller.Controller
47-
ctrlOptions controller.Options
48-
name string
40+
apiType simpleWatch
41+
mgr manager.Manager
42+
globalPredicates []predicate.Predicate
43+
managedObjects []simpleWatch
44+
watchRequest []watchRequest
45+
config *rest.Config
46+
ctrl controller.Controller
47+
ctrlOptions controller.Options
48+
name string
4949
}
5050

5151
// ControllerManagedBy returns a new controller builder that will be started by the provided Manager
@@ -63,32 +63,46 @@ func (blder *Builder) ForType(apiType runtime.Object) *Builder {
6363
return blder.For(apiType)
6464
}
6565

66+
// simpleWatch represents the information required to construct a "simple" For
67+
// or Owns watch.
68+
type simpleWatch struct {
69+
item runtime.Object
70+
predicates []predicate.Predicate
71+
}
72+
6673
// For defines the type of Object being *reconciled*, and configures the ControllerManagedBy to respond to create / delete /
6774
// update events by *reconciling the object*.
6875
// This is the equivalent of calling
6976
// Watches(&source.Kind{Type: apiType}, &handler.EnqueueRequestForObject{})
70-
func (blder *Builder) For(apiType runtime.Object) *Builder {
71-
blder.apiType = apiType
77+
func (blder *Builder) For(reconciledAPIType runtime.Object, prct ...predicate.Predicate) *Builder {
78+
blder.apiType = simpleWatch{
79+
item: reconciledAPIType,
80+
predicates: prct,
81+
}
7282
return blder
7383
}
7484

7585
// Owns defines types of Objects being *generated* by the ControllerManagedBy, and configures the ControllerManagedBy to respond to
7686
// create / delete / update events by *reconciling the owner object*. This is the equivalent of calling
7787
// Watches(&source.Kind{Type: <ForType-apiType>}, &handler.EnqueueRequestForOwner{OwnerType: apiType, IsController: true})
78-
func (blder *Builder) Owns(apiType runtime.Object) *Builder {
79-
blder.managedObjects = append(blder.managedObjects, apiType)
88+
func (blder *Builder) Owns(apiType runtime.Object, prct ...predicate.Predicate) *Builder {
89+
blder.managedObjects = append(blder.managedObjects, simpleWatch{item: apiType, predicates: prct})
8090
return blder
8191
}
8292

93+
// watchRequest represents the information needed to construct a
94+
// manually-defined (non-"simple") watch.
8395
type watchRequest struct {
8496
src source.Source
8597
eventhandler handler.EventHandler
98+
predicates []predicate.Predicate
8699
}
87100

88101
// Watches exposes the lower-level ControllerManagedBy Watches functions through the builder. Consider using
89102
// Owns or For instead of Watches directly.
90-
func (blder *Builder) Watches(src source.Source, eventhandler handler.EventHandler) *Builder {
91-
blder.watchRequest = append(blder.watchRequest, watchRequest{src: src, eventhandler: eventhandler})
103+
// Specified predicates are registered only for given source.
104+
func (blder *Builder) Watches(src source.Source, eventhandler handler.EventHandler, prct ...predicate.Predicate) *Builder {
105+
blder.watchRequest = append(blder.watchRequest, watchRequest{src: src, eventhandler: eventhandler, predicates: prct})
92106
return blder
93107
}
94108

@@ -102,9 +116,10 @@ func (blder *Builder) WithConfig(config *rest.Config) *Builder {
102116

103117
// WithEventFilter sets the event filters, to filter which create/update/delete/generic events eventually
104118
// trigger reconciliations. For example, filtering on whether the resource version has changed.
119+
// Given predicate is added for all watched objects.
105120
// Defaults to the empty list.
106121
func (blder *Builder) WithEventFilter(p predicate.Predicate) *Builder {
107-
blder.predicates = append(blder.predicates, p)
122+
blder.globalPredicates = append(blder.globalPredicates, p)
108123
return blder
109124
}
110125

@@ -157,28 +172,33 @@ func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, erro
157172

158173
func (blder *Builder) doWatch() error {
159174
// Reconcile type
160-
src := &source.Kind{Type: blder.apiType}
175+
src := &source.Kind{Type: blder.apiType.item}
161176
hdler := &handler.EnqueueRequestForObject{}
162-
err := blder.ctrl.Watch(src, hdler, blder.predicates...)
177+
allPredicates := append(blder.globalPredicates, blder.apiType.predicates...)
178+
err := blder.ctrl.Watch(src, hdler, allPredicates...)
163179
if err != nil {
164180
return err
165181
}
166182

167183
// Watches the managed types
168184
for _, obj := range blder.managedObjects {
169-
src := &source.Kind{Type: obj}
185+
src := &source.Kind{Type: obj.item}
170186
hdler := &handler.EnqueueRequestForOwner{
171-
OwnerType: blder.apiType,
187+
OwnerType: blder.apiType.item,
172188
IsController: true,
173189
}
174-
if err := blder.ctrl.Watch(src, hdler, blder.predicates...); err != nil {
190+
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
191+
allPredicates = append(allPredicates, obj.predicates...)
192+
if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
175193
return err
176194
}
177195
}
178196

179197
// Do the watch requests
180198
for _, w := range blder.watchRequest {
181-
if err := blder.ctrl.Watch(w.src, w.eventhandler, blder.predicates...); err != nil {
199+
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
200+
allPredicates = append(allPredicates, w.predicates...)
201+
if err := blder.ctrl.Watch(w.src, w.eventhandler, allPredicates...); err != nil {
182202
return err
183203
}
184204

@@ -196,7 +216,7 @@ func (blder *Builder) getControllerName() (string, error) {
196216
if blder.name != "" {
197217
return blder.name, nil
198218
}
199-
gvk, err := getGvk(blder.apiType, blder.mgr.GetScheme())
219+
gvk, err := getGvk(blder.apiType.item, blder.mgr.GetScheme())
200220
if err != nil {
201221
return "", err
202222
}

pkg/builder/controller_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ import (
3131
"k8s.io/apimachinery/pkg/runtime/schema"
3232
"k8s.io/apimachinery/pkg/types"
3333
"sigs.k8s.io/controller-runtime/pkg/controller"
34+
"sigs.k8s.io/controller-runtime/pkg/event"
3435
"sigs.k8s.io/controller-runtime/pkg/handler"
3536
"sigs.k8s.io/controller-runtime/pkg/manager"
37+
"sigs.k8s.io/controller-runtime/pkg/predicate"
3638
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3739
"sigs.k8s.io/controller-runtime/pkg/scheme"
3840
"sigs.k8s.io/controller-runtime/pkg/source"
@@ -180,6 +182,67 @@ var _ = Describe("application", func() {
180182
close(done)
181183
}, 10)
182184
})
185+
186+
Describe("Set custom predicates", func() {
187+
It("should execute registered predicates only for assigned kind", func(done Done) {
188+
m, err := manager.New(cfg, manager.Options{})
189+
Expect(err).NotTo(HaveOccurred())
190+
191+
var (
192+
deployPrctExecuted = false
193+
replicaSetPrctExecuted = false
194+
allPrctExecuted = 0
195+
)
196+
197+
deployPrct := predicate.Funcs{
198+
CreateFunc: func(e event.CreateEvent) bool {
199+
defer GinkgoRecover()
200+
// check that it was called only for deployment
201+
Expect(e.Meta).To(BeAssignableToTypeOf(&appsv1.Deployment{}))
202+
deployPrctExecuted = true
203+
return true
204+
},
205+
}
206+
207+
replicaSetPrct := predicate.Funcs{
208+
CreateFunc: func(e event.CreateEvent) bool {
209+
defer GinkgoRecover()
210+
// check that it was called only for replicaset
211+
Expect(e.Meta).To(BeAssignableToTypeOf(&appsv1.ReplicaSet{}))
212+
replicaSetPrctExecuted = true
213+
return true
214+
},
215+
}
216+
217+
allPrct := predicate.Funcs{
218+
CreateFunc: func(e event.CreateEvent) bool {
219+
defer GinkgoRecover()
220+
//check that it was called for all registered kinds
221+
Expect(e.Meta).Should(Or(
222+
BeAssignableToTypeOf(&appsv1.Deployment{}),
223+
BeAssignableToTypeOf(&appsv1.ReplicaSet{}),
224+
))
225+
226+
allPrctExecuted++
227+
return true
228+
},
229+
}
230+
231+
bldr := ControllerManagedBy(m).
232+
For(&appsv1.Deployment{}, deployPrct).
233+
Owns(&appsv1.ReplicaSet{}, replicaSetPrct).
234+
WithEventFilter(allPrct)
235+
236+
doReconcileTest("5", stop, bldr, m, true)
237+
238+
Expect(deployPrctExecuted).To(BeTrue(), "Deploy predicated should be called at least once")
239+
Expect(replicaSetPrctExecuted).To(BeTrue(), "ReplicaSet predicated should be called at least once")
240+
Expect(allPrctExecuted).To(BeNumerically(">=", 2), "Global Predicated should be called at least twice")
241+
242+
close(done)
243+
})
244+
})
245+
183246
})
184247

185248
func doReconcileTest(nameSuffix string, stop chan struct{}, blder *Builder, mgr manager.Manager, complete bool) {

0 commit comments

Comments
 (0)