Skip to content
This repository was archived by the owner on Oct 13, 2021. It is now read-only.

Commit 425ba00

Browse files
authored
Merge pull request #64 from pusher/fix-cgto-child-watch
Fix bug where multiple controllers attempt to reconcile CGTOs
2 parents de756a7 + 274ca6d commit 425ba00

File tree

3 files changed

+114
-24
lines changed

3 files changed

+114
-24
lines changed

pkg/controller/gittrackobject/gittrackobject_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
136136
},
137137
RestMapper: restMapper,
138138
},
139+
utils.NewOwnersOwnerInNamespacePredicate(mgr.GetClient()),
139140
)
140141
if err != nil {
141142
msg := fmt.Sprintf("unable to watch channel: %v", err)

pkg/controller/gittrackobject/gittrackobject_controller_test.go

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,14 @@ var (
391391
ObjectMeta: metav1.ObjectMeta{
392392
Name: "example",
393393
Namespace: "default",
394+
OwnerReferences: []metav1.OwnerReference{
395+
{
396+
APIVersion: "faros.pusher.com/v1alpha1",
397+
Kind: "GitTrack",
398+
UID: gitTrack.UID,
399+
Name: gitTrack.Name,
400+
},
401+
},
394402
},
395403
Spec: farosv1alpha1.GitTrackObjectSpec{
396404
Name: "deployment-example",
@@ -650,6 +658,10 @@ var (
650658
var ns *v1.Namespace
651659
var gt *farosv1alpha1.GitTrack
652660
BeforeEach(func() {
661+
CreateClusterInstance([]byte(exampleClusterRoleBinding))
662+
Eventually(requests, timeout).Should(Receive(Equal(expectedClusterRequest)))
663+
Eventually(requests, timeout).Should(Receive(Equal(expectedClusterRequest)))
664+
653665
ns = &v1.Namespace{
654666
ObjectMeta: metav1.ObjectMeta{
655667
Name: "not-default",
@@ -669,34 +681,32 @@ var (
669681
}
670682
Expect(c.Create(context.TODO(), gt)).NotTo(HaveOccurred())
671683

672-
clusterInstance = &farosv1alpha1.ClusterGitTrackObject{
673-
ObjectMeta: metav1.ObjectMeta{
674-
Name: "example",
675-
OwnerReferences: []metav1.OwnerReference{
676-
{
677-
APIVersion: "faros.pusher.com/v1alpha1",
678-
Kind: "GitTrack",
679-
UID: gt.UID,
680-
Name: gt.Name,
681-
},
682-
},
683-
},
684-
Spec: farosv1alpha1.GitTrackObjectSpec{
685-
Name: "clusterrolebinding-example",
686-
Kind: "ClusterRoleBinding",
687-
Data: []byte(exampleClusterRoleBinding),
684+
key := types.NamespacedName{Name: "example"}
685+
Expect(c.Get(context.TODO(), key, clusterInstance)).NotTo(HaveOccurred())
686+
clusterInstance.SetOwnerReferences([]metav1.OwnerReference{
687+
{
688+
APIVersion: "faros.pusher.com/v1alpha1",
689+
Kind: "GitTrack",
690+
Name: gt.Name,
691+
UID: gt.UID,
688692
},
689-
}
690-
Expect(c.Create(context.TODO(), clusterInstance)).NotTo(HaveOccurred())
693+
})
694+
Expect(c.Update(context.TODO(), clusterInstance)).NotTo(HaveOccurred())
691695
})
692696

693-
AfterEach(func() {
694-
Expect(c.Delete(context.TODO(), gt)).NotTo(HaveOccurred())
695-
Expect(c.Delete(context.TODO(), clusterInstance)).NotTo(HaveOccurred())
697+
It("should not reconcile it", func() {
698+
Consistently(requests).ShouldNot(Receive())
696699
})
697700

698-
It("should not reconcile it", func() {
699-
Eventually(requests, timeout).ShouldNot(Receive())
701+
It("should not reconcile when the child is modified", func() {
702+
crb := &rbacv1.ClusterRoleBinding{}
703+
key := types.NamespacedName{Name: "example"}
704+
Expect(c.Get(context.TODO(), key, crb)).NotTo(HaveOccurred())
705+
706+
crb.SetLabels(map[string]string{})
707+
Expect(c.Update(context.TODO(), crb)).NotTo(HaveOccurred())
708+
709+
Consistently(requests).ShouldNot(Receive())
700710
})
701711
}
702712

pkg/utils/predicate.go

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import (
2525
"sigs.k8s.io/controller-runtime/pkg/event"
2626
)
2727

28+
const (
29+
farosGroupVersion = "faros.pusher.com/v1alpha1"
30+
)
31+
2832
// OwnerInNamespacePredicate filters events to check the owner of the event
2933
// object is in the controller's namespace
3034
type OwnerInNamespacePredicate struct {
@@ -66,7 +70,7 @@ func (p OwnerInNamespacePredicate) ownerInNamespace(ownerRefs []metav1.OwnerRefe
6670
return false
6771
}
6872
for _, ref := range ownerRefs {
69-
if ref.Kind == "GitTrack" && ref.APIVersion == "faros.pusher.com/v1alpha1" {
73+
if ref.Kind == "GitTrack" && ref.APIVersion == farosGroupVersion {
7074
for _, gt := range gtList.Items {
7175
if ref.UID == gt.UID {
7276
return true
@@ -83,3 +87,78 @@ func NewOwnerInNamespacePredicate(client client.Client) OwnerInNamespacePredicat
8387
client: client,
8488
}
8589
}
90+
91+
// OwnersOwnerInNamespacePredicate filters events to check the owners owner of
92+
// the event object is in the controller's namespace
93+
type OwnersOwnerInNamespacePredicate struct {
94+
client client.Client
95+
ownerInNamespacePredicate OwnerInNamespacePredicate
96+
}
97+
98+
// Create returns true if the event object owners owner is in the same namespace
99+
func (p OwnersOwnerInNamespacePredicate) Create(e event.CreateEvent) bool {
100+
return p.ownersOwnerInNamespace(e.Meta.GetOwnerReferences())
101+
}
102+
103+
// Update returns true if the event object owners owner is in the same namespace
104+
func (p OwnersOwnerInNamespacePredicate) Update(e event.UpdateEvent) bool {
105+
return p.ownersOwnerInNamespace(e.MetaNew.GetOwnerReferences())
106+
}
107+
108+
// Delete returns true if the event object owners owner is in the same namespace
109+
func (p OwnersOwnerInNamespacePredicate) Delete(e event.DeleteEvent) bool {
110+
return p.ownersOwnerInNamespace(e.Meta.GetOwnerReferences())
111+
}
112+
113+
// Generic returns true if the event object owners owner is in the same namespace
114+
func (p OwnersOwnerInNamespacePredicate) Generic(e event.GenericEvent) bool {
115+
return p.ownersOwnerInNamespace(e.Meta.GetOwnerReferences())
116+
}
117+
118+
// ownersOwnerInNamespace returns true if the the GitTrackObject's GitTrack
119+
// owner of the event object is in the namespace managed by the controller
120+
//
121+
// This works on the premise that listing objects from the client will only
122+
// return those in its cache.
123+
// When it is restricted to a namespace this should only be the GitTracks
124+
// in the namespace the controller is managing.
125+
func (p OwnersOwnerInNamespacePredicate) ownersOwnerInNamespace(ownerRefs []metav1.OwnerReference) bool {
126+
cgtoList := &farosv1alpha1.ClusterGitTrackObjectList{}
127+
err := p.client.List(context.TODO(), &client.ListOptions{}, cgtoList)
128+
if err != nil {
129+
// We can't list CGTOs so fail closed and ignore the requests
130+
return false
131+
}
132+
gtoList := &farosv1alpha1.GitTrackObjectList{}
133+
err = p.client.List(context.TODO(), &client.ListOptions{}, gtoList)
134+
if err != nil {
135+
// We can't list GTOs so fail closed and ignore the requests
136+
return false
137+
}
138+
139+
for _, ref := range ownerRefs {
140+
if ref.Kind == "GitTrackObject" && ref.APIVersion == farosGroupVersion {
141+
for _, gto := range gtoList.Items {
142+
if ref.UID == gto.UID {
143+
return p.ownerInNamespacePredicate.ownerInNamespace(gto.GetOwnerReferences())
144+
}
145+
}
146+
}
147+
if ref.Kind == "ClusterGitTrackObject" && ref.APIVersion == farosGroupVersion {
148+
for _, cgto := range cgtoList.Items {
149+
if ref.UID == cgto.UID {
150+
return p.ownerInNamespacePredicate.ownerInNamespace(cgto.GetOwnerReferences())
151+
}
152+
}
153+
}
154+
}
155+
return false
156+
}
157+
158+
// NewOwnersOwnerInNamespacePredicate constructs a new OwnersOwnerInNamespacePredicate
159+
func NewOwnersOwnerInNamespacePredicate(client client.Client) OwnersOwnerInNamespacePredicate {
160+
return OwnersOwnerInNamespacePredicate{
161+
client: client,
162+
ownerInNamespacePredicate: NewOwnerInNamespacePredicate(client),
163+
}
164+
}

0 commit comments

Comments
 (0)