Skip to content

Commit a07ed0d

Browse files
committed
Add predicate for annotations change on update event
Signed-off-by: Zvi Cahana <[email protected]>
1 parent ac788de commit a07ed0d

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed

pkg/predicate/predicate.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package predicate
1818

1919
import (
20+
"reflect"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2123
"k8s.io/apimachinery/pkg/labels"
2224
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -44,6 +46,7 @@ type Predicate interface {
4446
var _ Predicate = Funcs{}
4547
var _ Predicate = ResourceVersionChangedPredicate{}
4648
var _ Predicate = GenerationChangedPredicate{}
49+
var _ Predicate = AnnotationChangedPredicate{}
4750
var _ Predicate = or{}
4851
var _ Predicate = and{}
4952

@@ -167,6 +170,36 @@ func (GenerationChangedPredicate) Update(e event.UpdateEvent) bool {
167170
return e.ObjectNew.GetGeneration() != e.ObjectOld.GetGeneration()
168171
}
169172

173+
// AnnotationChangedPredicate implements a default update predicate function on annotation change.
174+
//
175+
// This predicate will skip update events that have no change in the object's annotation.
176+
// It is intended to be used in conjunction with the GenerationChangedPredicate, as in the following example:
177+
//
178+
// Controller.Watch(
179+
// &source.Kind{Type: v1.MyCustomKind},
180+
// &handler.EnqueueRequestForObject{},
181+
// predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}))
182+
//
183+
// This is mostly useful for controllers that needs to trigger both when the resource's generation is incremented
184+
// (i.e., when the resource' .spec changes), or an annotation changes (e.g., for a staging/alpha API).
185+
type AnnotationChangedPredicate struct {
186+
Funcs
187+
}
188+
189+
// Update implements default UpdateEvent filter for validating annotation change
190+
func (AnnotationChangedPredicate) Update(e event.UpdateEvent) bool {
191+
if e.ObjectOld == nil {
192+
log.Error(nil, "Update event has no old object to update", "event", e)
193+
return false
194+
}
195+
if e.ObjectNew == nil {
196+
log.Error(nil, "Update event has no new object for update", "event", e)
197+
return false
198+
}
199+
200+
return !reflect.DeepEqual(e.ObjectNew.GetAnnotations(), e.ObjectOld.GetAnnotations())
201+
}
202+
170203
// And returns a composite predicate that implements a logical AND of the predicates passed to it.
171204
func And(predicates ...Predicate) Predicate {
172205
return and{predicates}

pkg/predicate/predicate_test.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,202 @@ var _ = Describe("Predicate", func() {
414414

415415
})
416416

417+
Describe("When checking an AnnotationChangedPredicate", func() {
418+
instance := predicate.AnnotationChangedPredicate{}
419+
Context("Where the old object is missing", func() {
420+
It("should return false", func() {
421+
new := &corev1.Pod{
422+
ObjectMeta: metav1.ObjectMeta{
423+
Name: "baz",
424+
Namespace: "biz",
425+
Annotations: map[string]string{
426+
"booz": "wooz",
427+
},
428+
}}
429+
430+
failEvnt := event.UpdateEvent{
431+
ObjectNew: new,
432+
}
433+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
434+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
435+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
436+
Expect(instance.Update(failEvnt)).To(BeFalse())
437+
})
438+
})
439+
440+
Context("Where the new object is missing", func() {
441+
It("should return false", func() {
442+
old := &corev1.Pod{
443+
ObjectMeta: metav1.ObjectMeta{
444+
Name: "baz",
445+
Namespace: "biz",
446+
Annotations: map[string]string{
447+
"booz": "wooz",
448+
},
449+
}}
450+
451+
failEvnt := event.UpdateEvent{
452+
ObjectOld: old,
453+
}
454+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
455+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
456+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
457+
Expect(instance.Update(failEvnt)).To(BeFalse())
458+
})
459+
})
460+
461+
Context("Where the annotations are empty", func() {
462+
It("should return false", func() {
463+
new := &corev1.Pod{
464+
ObjectMeta: metav1.ObjectMeta{
465+
Name: "baz",
466+
Namespace: "biz",
467+
}}
468+
469+
old := &corev1.Pod{
470+
ObjectMeta: metav1.ObjectMeta{
471+
Name: "baz",
472+
Namespace: "biz",
473+
}}
474+
475+
failEvnt := event.UpdateEvent{
476+
ObjectOld: old,
477+
ObjectNew: new,
478+
}
479+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
480+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
481+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
482+
Expect(instance.Update(failEvnt)).To(BeFalse())
483+
})
484+
})
485+
486+
Context("Where the annotations haven't changed", func() {
487+
It("should return false", func() {
488+
new := &corev1.Pod{
489+
ObjectMeta: metav1.ObjectMeta{
490+
Name: "baz",
491+
Namespace: "biz",
492+
Annotations: map[string]string{
493+
"booz": "wooz",
494+
},
495+
}}
496+
497+
old := &corev1.Pod{
498+
ObjectMeta: metav1.ObjectMeta{
499+
Name: "baz",
500+
Namespace: "biz",
501+
Annotations: map[string]string{
502+
"booz": "wooz",
503+
},
504+
}}
505+
506+
failEvnt := event.UpdateEvent{
507+
ObjectOld: old,
508+
ObjectNew: new,
509+
}
510+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
511+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
512+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
513+
Expect(instance.Update(failEvnt)).To(BeFalse())
514+
})
515+
})
516+
517+
Context("Where an annotation value has changed", func() {
518+
It("should return true", func() {
519+
new := &corev1.Pod{
520+
ObjectMeta: metav1.ObjectMeta{
521+
Name: "baz",
522+
Namespace: "biz",
523+
Annotations: map[string]string{
524+
"booz": "wooz",
525+
},
526+
}}
527+
528+
old := &corev1.Pod{
529+
ObjectMeta: metav1.ObjectMeta{
530+
Name: "baz",
531+
Namespace: "biz",
532+
Annotations: map[string]string{
533+
"booz": "weez",
534+
},
535+
}}
536+
537+
passEvt := event.UpdateEvent{
538+
ObjectOld: old,
539+
ObjectNew: new,
540+
}
541+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
542+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
543+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
544+
Expect(instance.Update(passEvt)).To(BeTrue())
545+
})
546+
})
547+
548+
Context("Where an annotation has been added", func() {
549+
It("should return true", func() {
550+
new := &corev1.Pod{
551+
ObjectMeta: metav1.ObjectMeta{
552+
Name: "baz",
553+
Namespace: "biz",
554+
Annotations: map[string]string{
555+
"booz": "wooz",
556+
},
557+
}}
558+
559+
old := &corev1.Pod{
560+
ObjectMeta: metav1.ObjectMeta{
561+
Name: "baz",
562+
Namespace: "biz",
563+
Annotations: map[string]string{
564+
"booz": "wooz",
565+
"zooz": "qooz",
566+
},
567+
}}
568+
569+
passEvt := event.UpdateEvent{
570+
ObjectOld: old,
571+
ObjectNew: new,
572+
}
573+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
574+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
575+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
576+
Expect(instance.Update(passEvt)).To(BeTrue())
577+
})
578+
})
579+
580+
Context("Where an annotation has been removed", func() {
581+
It("should return true", func() {
582+
new := &corev1.Pod{
583+
ObjectMeta: metav1.ObjectMeta{
584+
Name: "baz",
585+
Namespace: "biz",
586+
Annotations: map[string]string{
587+
"booz": "wooz",
588+
"zooz": "qooz",
589+
},
590+
}}
591+
592+
old := &corev1.Pod{
593+
ObjectMeta: metav1.ObjectMeta{
594+
Name: "baz",
595+
Namespace: "biz",
596+
Annotations: map[string]string{
597+
"booz": "wooz",
598+
},
599+
}}
600+
601+
passEvt := event.UpdateEvent{
602+
ObjectOld: old,
603+
ObjectNew: new,
604+
}
605+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
606+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
607+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
608+
Expect(instance.Update(passEvt)).To(BeTrue())
609+
})
610+
})
611+
})
612+
417613
Context("With a boolean predicate", func() {
418614
funcs := func(pass bool) predicate.Funcs {
419615
return predicate.Funcs{

0 commit comments

Comments
 (0)