Skip to content

Commit fe4a67a

Browse files
authored
✨ introduce LabelChangedPredicate to trigger event on label change (#1315)
* introduce LabelChangedPredicate to trigger event on label change Signed-off-by: Bruce Ma <[email protected]> * disable duplication linter on predicate tests Signed-off-by: Bruce Ma <[email protected]>
1 parent 2750bdd commit fe4a67a

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

pkg/predicate/predicate.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,36 @@ func (AnnotationChangedPredicate) Update(e event.UpdateEvent) bool {
200200
return !reflect.DeepEqual(e.ObjectNew.GetAnnotations(), e.ObjectOld.GetAnnotations())
201201
}
202202

203+
// LabelChangedPredicate implements a default update predicate function on label change.
204+
//
205+
// This predicate will skip update events that have no change in the object's label.
206+
// It is intended to be used in conjunction with the GenerationChangedPredicate, as in the following example:
207+
//
208+
// Controller.Watch(
209+
// &source.Kind{Type: v1.MyCustomKind},
210+
// &handler.EnqueueRequestForObject{},
211+
// predicate.Or(predicate.GenerationChangedPredicate{}, predicate.LabelChangedPredicate{}))
212+
//
213+
// This will be helpful when object's labels is carrying some extra specification information beyond object's spec,
214+
// and the controller will be triggered if any valid spec change (not only in spec, but also in labels) happens.
215+
type LabelChangedPredicate struct {
216+
Funcs
217+
}
218+
219+
// Update implements default UpdateEvent filter for checking label change
220+
func (LabelChangedPredicate) Update(e event.UpdateEvent) bool {
221+
if e.ObjectOld == nil {
222+
log.Error(nil, "Update event has no old object to update", "event", e)
223+
return false
224+
}
225+
if e.ObjectNew == nil {
226+
log.Error(nil, "Update event has no new object for update", "event", e)
227+
return false
228+
}
229+
230+
return !reflect.DeepEqual(e.ObjectNew.GetLabels(), e.ObjectOld.GetLabels())
231+
}
232+
203233
// And returns a composite predicate that implements a logical AND of the predicates passed to it.
204234
func And(predicates ...Predicate) Predicate {
205235
return and{predicates}

pkg/predicate/predicate_test.go

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

415415
})
416416

417+
// AnnotationChangedPredicate has almost identical test cases as LabelChangedPredicates,
418+
// so the duplication linter should be muted on both two test suites.
419+
// nolint:dupl
417420
Describe("When checking an AnnotationChangedPredicate", func() {
418421
instance := predicate.AnnotationChangedPredicate{}
419422
Context("Where the old object is missing", func() {
@@ -610,6 +613,205 @@ var _ = Describe("Predicate", func() {
610613
})
611614
})
612615

616+
// LabelChangedPredicates has almost identical test cases as AnnotationChangedPredicates,
617+
// so the duplication linter should be muted on both two test suites.
618+
// nolint:dupl
619+
Describe("When checking a LabelChangedPredicate", func() {
620+
instance := predicate.LabelChangedPredicate{}
621+
Context("Where the old object is missing", func() {
622+
It("should return false", func() {
623+
new := &corev1.Pod{
624+
ObjectMeta: metav1.ObjectMeta{
625+
Name: "baz",
626+
Namespace: "biz",
627+
Labels: map[string]string{
628+
"foo": "bar",
629+
},
630+
}}
631+
632+
evt := event.UpdateEvent{
633+
ObjectNew: new,
634+
}
635+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
636+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
637+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
638+
Expect(instance.Update(evt)).To(BeFalse())
639+
})
640+
})
641+
642+
Context("Where the new object is missing", func() {
643+
It("should return false", func() {
644+
old := &corev1.Pod{
645+
ObjectMeta: metav1.ObjectMeta{
646+
Name: "baz",
647+
Namespace: "biz",
648+
Labels: map[string]string{
649+
"foo": "bar",
650+
},
651+
}}
652+
653+
evt := event.UpdateEvent{
654+
ObjectOld: old,
655+
}
656+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
657+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
658+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
659+
Expect(instance.Update(evt)).To(BeFalse())
660+
})
661+
})
662+
663+
Context("Where the labels are empty", func() {
664+
It("should return false", func() {
665+
new := &corev1.Pod{
666+
ObjectMeta: metav1.ObjectMeta{
667+
Name: "baz",
668+
Namespace: "biz",
669+
}}
670+
671+
old := &corev1.Pod{
672+
ObjectMeta: metav1.ObjectMeta{
673+
Name: "baz",
674+
Namespace: "biz",
675+
}}
676+
677+
evt := event.UpdateEvent{
678+
ObjectOld: old,
679+
ObjectNew: new,
680+
}
681+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
682+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
683+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
684+
Expect(instance.Update(evt)).To(BeFalse())
685+
})
686+
})
687+
688+
Context("Where the labels haven't changed", func() {
689+
It("should return false", func() {
690+
new := &corev1.Pod{
691+
ObjectMeta: metav1.ObjectMeta{
692+
Name: "baz",
693+
Namespace: "biz",
694+
Labels: map[string]string{
695+
"foo": "bar",
696+
},
697+
}}
698+
699+
old := &corev1.Pod{
700+
ObjectMeta: metav1.ObjectMeta{
701+
Name: "baz",
702+
Namespace: "biz",
703+
Labels: map[string]string{
704+
"foo": "bar",
705+
},
706+
}}
707+
708+
evt := event.UpdateEvent{
709+
ObjectOld: old,
710+
ObjectNew: new,
711+
}
712+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
713+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
714+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
715+
Expect(instance.Update(evt)).To(BeFalse())
716+
})
717+
})
718+
719+
Context("Where a label value has changed", func() {
720+
It("should return true", func() {
721+
new := &corev1.Pod{
722+
ObjectMeta: metav1.ObjectMeta{
723+
Name: "baz",
724+
Namespace: "biz",
725+
Labels: map[string]string{
726+
"foo": "bar",
727+
},
728+
}}
729+
730+
old := &corev1.Pod{
731+
ObjectMeta: metav1.ObjectMeta{
732+
Name: "baz",
733+
Namespace: "biz",
734+
Labels: map[string]string{
735+
"foo": "bee",
736+
},
737+
}}
738+
739+
evt := event.UpdateEvent{
740+
ObjectOld: old,
741+
ObjectNew: new,
742+
}
743+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
744+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
745+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
746+
Expect(instance.Update(evt)).To(BeTrue())
747+
})
748+
})
749+
750+
Context("Where a label has been added", func() {
751+
It("should return true", func() {
752+
new := &corev1.Pod{
753+
ObjectMeta: metav1.ObjectMeta{
754+
Name: "baz",
755+
Namespace: "biz",
756+
Labels: map[string]string{
757+
"foo": "bar",
758+
},
759+
}}
760+
761+
old := &corev1.Pod{
762+
ObjectMeta: metav1.ObjectMeta{
763+
Name: "baz",
764+
Namespace: "biz",
765+
Labels: map[string]string{
766+
"foo": "bar",
767+
"faa": "bor",
768+
},
769+
}}
770+
771+
evt := event.UpdateEvent{
772+
ObjectOld: old,
773+
ObjectNew: new,
774+
}
775+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
776+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
777+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
778+
Expect(instance.Update(evt)).To(BeTrue())
779+
})
780+
})
781+
782+
Context("Where a label has been removed", func() {
783+
It("should return true", func() {
784+
new := &corev1.Pod{
785+
ObjectMeta: metav1.ObjectMeta{
786+
Name: "baz",
787+
Namespace: "biz",
788+
Labels: map[string]string{
789+
"foo": "bar",
790+
"faa": "bor",
791+
},
792+
}}
793+
794+
old := &corev1.Pod{
795+
ObjectMeta: metav1.ObjectMeta{
796+
Name: "baz",
797+
Namespace: "biz",
798+
Labels: map[string]string{
799+
"foo": "bar",
800+
},
801+
}}
802+
803+
evt := event.UpdateEvent{
804+
ObjectOld: old,
805+
ObjectNew: new,
806+
}
807+
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
808+
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
809+
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
810+
Expect(instance.Update(evt)).To(BeTrue())
811+
})
812+
})
813+
})
814+
613815
Context("With a boolean predicate", func() {
614816
funcs := func(pass bool) predicate.Funcs {
615817
return predicate.Funcs{

0 commit comments

Comments
 (0)