Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add predicate for annotations change on update event #1254

Merged
merged 2 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 36 additions & 17 deletions pkg/predicate/predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package predicate

import (
"reflect"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -44,6 +46,7 @@ type Predicate interface {
var _ Predicate = Funcs{}
var _ Predicate = ResourceVersionChangedPredicate{}
var _ Predicate = GenerationChangedPredicate{}
var _ Predicate = AnnotationChangedPredicate{}
var _ Predicate = or{}
var _ Predicate = and{}

Expand Down Expand Up @@ -122,21 +125,14 @@ type ResourceVersionChangedPredicate struct {
// Update implements default UpdateEvent filter for validating resource version change
func (ResourceVersionChangedPredicate) Update(e event.UpdateEvent) bool {
if e.ObjectOld == nil {
log.Error(nil, "UpdateEvent has no old metadata", "event", e)
return false
}
if e.ObjectOld == nil {
log.Error(nil, "GenericEvent has no old runtime object to update", "event", e)
return false
}
if e.ObjectNew == nil {
log.Error(nil, "GenericEvent has no new runtime object for update", "event", e)
log.Error(nil, "Update event has no old object to update", "event", e)
return false
}
if e.ObjectNew == nil {
log.Error(nil, "UpdateEvent has no new metadata", "event", e)
log.Error(nil, "Update event has no new object to update", "event", e)
return false
}

return e.ObjectNew.GetResourceVersion() != e.ObjectOld.GetResourceVersion()
}

Expand All @@ -163,22 +159,45 @@ type GenerationChangedPredicate struct {
// Update implements default UpdateEvent filter for validating generation change
func (GenerationChangedPredicate) Update(e event.UpdateEvent) bool {
if e.ObjectOld == nil {
log.Error(nil, "Update event has no old metadata", "event", e)
log.Error(nil, "Update event has no old object to update", "event", e)
return false
}
if e.ObjectOld == nil {
log.Error(nil, "Update event has no old runtime object to update", "event", e)
if e.ObjectNew == nil {
log.Error(nil, "Update event has no new object for update", "event", e)
return false
}
if e.ObjectNew == nil {
log.Error(nil, "Update event has no new runtime object for update", "event", e)

return e.ObjectNew.GetGeneration() != e.ObjectOld.GetGeneration()
}

// AnnotationChangedPredicate implements a default update predicate function on annotation change.
//
// This predicate will skip update events that have no change in the object's annotation.
// It is intended to be used in conjunction with the GenerationChangedPredicate, as in the following example:
//
// Controller.Watch(
// &source.Kind{Type: v1.MyCustomKind},
// &handler.EnqueueRequestForObject{},
// predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}))
//
// This is mostly useful for controllers that needs to trigger both when the resource's generation is incremented
// (i.e., when the resource' .spec changes), or an annotation changes (e.g., for a staging/alpha API).
type AnnotationChangedPredicate struct {
Funcs
}

// Update implements default UpdateEvent filter for validating annotation change
func (AnnotationChangedPredicate) Update(e event.UpdateEvent) bool {
if e.ObjectOld == nil {
log.Error(nil, "Update event has no old object to update", "event", e)
return false
}
if e.ObjectNew == nil {
log.Error(nil, "Update event has no new metadata", "event", e)
log.Error(nil, "Update event has no new object for update", "event", e)
return false
}
return e.ObjectNew.GetGeneration() != e.ObjectOld.GetGeneration()

return !reflect.DeepEqual(e.ObjectNew.GetAnnotations(), e.ObjectOld.GetAnnotations())
}

// And returns a composite predicate that implements a logical AND of the predicates passed to it.
Expand Down
196 changes: 196 additions & 0 deletions pkg/predicate/predicate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,202 @@ var _ = Describe("Predicate", func() {

})

Describe("When checking an AnnotationChangedPredicate", func() {
instance := predicate.AnnotationChangedPredicate{}
Context("Where the old object is missing", func() {
It("should return false", func() {
new := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
},
}}

failEvnt := event.UpdateEvent{
ObjectNew: new,
}
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
Expect(instance.Update(failEvnt)).To(BeFalse())
})
})

Context("Where the new object is missing", func() {
It("should return false", func() {
old := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
},
}}

failEvnt := event.UpdateEvent{
ObjectOld: old,
}
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
Expect(instance.Update(failEvnt)).To(BeFalse())
})
})

Context("Where the annotations are empty", func() {
It("should return false", func() {
new := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
}}

old := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
}}

failEvnt := event.UpdateEvent{
ObjectOld: old,
ObjectNew: new,
}
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
Expect(instance.Update(failEvnt)).To(BeFalse())
})
})

Context("Where the annotations haven't changed", func() {
It("should return false", func() {
new := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
},
}}

old := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
},
}}

failEvnt := event.UpdateEvent{
ObjectOld: old,
ObjectNew: new,
}
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
Expect(instance.Update(failEvnt)).To(BeFalse())
})
})

Context("Where an annotation value has changed", func() {
It("should return true", func() {
new := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
},
}}

old := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "weez",
},
}}

passEvt := event.UpdateEvent{
ObjectOld: old,
ObjectNew: new,
}
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
Expect(instance.Update(passEvt)).To(BeTrue())
})
})

Context("Where an annotation has been added", func() {
It("should return true", func() {
new := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
},
}}

old := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
"zooz": "qooz",
},
}}

passEvt := event.UpdateEvent{
ObjectOld: old,
ObjectNew: new,
}
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
Expect(instance.Update(passEvt)).To(BeTrue())
})
})

Context("Where an annotation has been removed", func() {
It("should return true", func() {
new := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
"zooz": "qooz",
},
}}

old := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "biz",
Annotations: map[string]string{
"booz": "wooz",
},
}}

passEvt := event.UpdateEvent{
ObjectOld: old,
ObjectNew: new,
}
Expect(instance.Create(event.CreateEvent{})).To(BeTrue())
Expect(instance.Delete(event.DeleteEvent{})).To(BeTrue())
Expect(instance.Generic(event.GenericEvent{})).To(BeTrue())
Expect(instance.Update(passEvt)).To(BeTrue())
})
})
})

Context("With a boolean predicate", func() {
funcs := func(pass bool) predicate.Funcs {
return predicate.Funcs{
Expand Down