Skip to content

Commit

Permalink
Merge pull request #1254 from zcahana/annotation_predicate
Browse files Browse the repository at this point in the history
✨ Add predicate for annotations change on update event
  • Loading branch information
k8s-ci-robot committed Nov 9, 2020
2 parents 2b423ec + a07ed0d commit 114431a
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 17 deletions.
53 changes: 36 additions & 17 deletions pkg/predicate/predicate.go
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
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

0 comments on commit 114431a

Please sign in to comment.