Skip to content

Commit f634b54

Browse files
author
Phillip Wittrock
authored
Merge pull request #46 from pwittrock/transformations
Support mapping an object to multiple keys
2 parents 12f1fba + 7dced0a commit f634b54

File tree

4 files changed

+244
-5
lines changed

4 files changed

+244
-5
lines changed

pkg/controller/controller.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,44 @@ func (gc *GenericController) WatchTransformationOf(obj metav1.Object, mapFn even
124124
eventhandlers.MapAndEnqueue{Map: mapFn, Predicates: p})
125125
}
126126

127+
// WatchTransformationsOf watches objects matching obj's type and enqueues the keys returned by mapFn.
128+
func (gc *GenericController) WatchTransformationsOf(obj metav1.Object, mapFn eventhandlers.ObjToKeys,
129+
p ...predicates.Predicate) error {
130+
gc.once.Do(gc.init)
131+
return gc.queue.addEventHandler(obj,
132+
eventhandlers.MapAndEnqueue{MultiMap: func(i interface{}) []types.ReconcileKey {
133+
result := []types.ReconcileKey{}
134+
for _, k := range mapFn(i) {
135+
if namespace, name, err := cache.SplitMetaNamespaceKey(k); err == nil {
136+
result = append(result, types.ReconcileKey{namespace, name})
137+
}
138+
}
139+
return result
140+
}, Predicates: p})
141+
}
142+
143+
// WatchTransformationKeyOf watches objects matching obj's type and enqueues the key returned by mapFn.
144+
func (gc *GenericController) WatchTransformationKeyOf(obj metav1.Object, mapFn eventhandlers.ObjToReconcileKey,
145+
p ...predicates.Predicate) error {
146+
gc.once.Do(gc.init)
147+
return gc.queue.addEventHandler(obj,
148+
eventhandlers.MapAndEnqueue{MultiMap: func(i interface{}) []types.ReconcileKey {
149+
if k := mapFn(i); len(k.Name) > 0 {
150+
return []types.ReconcileKey{k}
151+
} else {
152+
return []types.ReconcileKey{}
153+
}
154+
}, Predicates: p})
155+
}
156+
157+
// WatchTransformationKeysOf watches objects matching obj's type and enqueues the keys returned by mapFn.
158+
func (gc *GenericController) WatchTransformationKeysOf(obj metav1.Object, mapFn eventhandlers.ObjToReconcileKeys,
159+
p ...predicates.Predicate) error {
160+
gc.once.Do(gc.init)
161+
return gc.queue.addEventHandler(obj,
162+
eventhandlers.MapAndEnqueue{MultiMap: mapFn, Predicates: p})
163+
}
164+
127165
// WatchEvents watches objects matching obj's type and uses the functions from provider to handle events.
128166
func (gc *GenericController) WatchEvents(obj metav1.Object, provider types.HandleFnProvider) error {
129167
gc.once.Do(gc.init)

pkg/controller/controller_test.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
. "github.com/onsi/ginkgo"
2121
. "github.com/onsi/gomega"
2222

23+
"time"
24+
2325
"github.com/kubernetes-sigs/kubebuilder/pkg/controller/eventhandlers"
2426
"github.com/kubernetes-sigs/kubebuilder/pkg/controller/test"
2527
"github.com/kubernetes-sigs/kubebuilder/pkg/controller/types"
@@ -29,7 +31,6 @@ import (
2931
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3032
"k8s.io/client-go/tools/cache"
3133
"k8s.io/client-go/util/workqueue"
32-
"time"
3334
)
3435

3536
var _ = Describe("GenericController", func() {
@@ -218,7 +219,7 @@ var _ = Describe("GenericController", func() {
218219
Expect(instance.GetMetrics().QueueLength).Should(Equal(0))
219220
})
220221

221-
It("should use the map function to reconcile a different key", func() {
222+
It("should use the transformation function to reconcile a different key", func() {
222223
// Listen for Pod changes
223224
Expect(instance.WatchTransformationOf(&corev1.Pod{}, func(obj interface{}) string {
224225
p := obj.(*corev1.Pod)
@@ -233,6 +234,60 @@ var _ = Describe("GenericController", func() {
233234
Expect(instance.GetMetrics().QueueLength).Should(Equal(0))
234235
})
235236

237+
It("should use the transformationkey function to reconcile a different key", func() {
238+
// Listen for Pod changes
239+
Expect(instance.WatchTransformationKeyOf(&corev1.Pod{}, func(obj interface{}) types.ReconcileKey {
240+
p := obj.(*corev1.Pod)
241+
return types.ReconcileKey{p.Namespace + "-namespace", p.Name + "-name"}
242+
})).Should(Succeed())
243+
244+
fakePodInformer.Add(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}})
245+
246+
val := ChannelResult{}
247+
Eventually(result).Should(Receive(&val.result))
248+
Expect(val.result).Should(Equal("default-namespace/test-pod-name"))
249+
Expect(instance.GetMetrics().QueueLength).Should(Equal(0))
250+
})
251+
252+
It("should use the transformationsof function to reconcile multiple different keys", func() {
253+
// Listen for Pod changes
254+
Expect(instance.WatchTransformationsOf(&corev1.Pod{}, func(obj interface{}) []string {
255+
p := obj.(*corev1.Pod)
256+
return []string{
257+
p.Namespace + "-namespace/" + p.Name + "-name-1",
258+
p.Namespace + "-namespace/" + p.Name + "-name-2"}
259+
})).Should(Succeed())
260+
261+
fakePodInformer.Add(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}})
262+
263+
val := ChannelResult{}
264+
Eventually(result).Should(Receive(&val.result))
265+
Expect(val.result).Should(Equal("default-namespace/test-pod-name-1"))
266+
Eventually(result).Should(Receive(&val.result))
267+
Expect(val.result).Should(Equal("default-namespace/test-pod-name-2"))
268+
Expect(instance.GetMetrics().QueueLength).Should(Equal(0))
269+
})
270+
271+
It("should use the transformationkeysof function to reconcile multiple different keys", func() {
272+
// Listen for Pod changes
273+
Expect(instance.WatchTransformationKeysOf(&corev1.Pod{}, func(obj interface{}) []types.ReconcileKey {
274+
p := obj.(*corev1.Pod)
275+
return []types.ReconcileKey{
276+
{p.Namespace + "-namespace", p.Name + "-name-1"},
277+
{p.Namespace + "-namespace", p.Name + "-name-2"},
278+
}
279+
})).Should(Succeed())
280+
281+
fakePodInformer.Add(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}})
282+
283+
val := ChannelResult{}
284+
Eventually(result).Should(Receive(&val.result))
285+
Expect(val.result).Should(Equal("default-namespace/test-pod-name-1"))
286+
Eventually(result).Should(Receive(&val.result))
287+
Expect(val.result).Should(Equal("default-namespace/test-pod-name-2"))
288+
Expect(instance.GetMetrics().QueueLength).Should(Equal(0))
289+
})
290+
236291
It("should call the event handling add function", func() {
237292
// Listen for Pod changes
238293
Expect(instance.WatchEvents(&corev1.Pod{},

pkg/controller/eventhandlers/eventhandlers.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ type MapAndEnqueue struct {
3939
Predicates []predicates.Predicate
4040
// Map maps an object to a key that can be enqueued
4141
Map func(interface{}) string
42+
43+
MultiMap func(interface{}) []types.ReconcileKey
4244
}
4345

4446
// Get returns ResourceEventHandlerFuncs that Map an object to a Key and enqueue the key if it is non-empty
@@ -74,9 +76,15 @@ func (mp MapAndEnqueue) Get(r workqueue.RateLimitingInterface) cache.ResourceEve
7476

7577
// addRateLimited maps the obj to a string. If the string is non-empty, it is enqueued.
7678
func (mp MapAndEnqueue) addRateLimited(r workqueue.RateLimitingInterface, obj interface{}) {
77-
k := mp.Map(obj)
78-
if len(k) > 0 {
79-
r.AddRateLimited(k)
79+
if mp.Map != nil {
80+
if k := mp.Map(obj); len(k) > 0 {
81+
r.AddRateLimited(k)
82+
}
83+
}
84+
if mp.MultiMap != nil {
85+
for _, k := range mp.MultiMap(obj) {
86+
r.AddRateLimited(k.Namespace + "/" + k.Name)
87+
}
8088
}
8189
}
8290

@@ -141,6 +149,12 @@ func (m MapToController) Map(obj interface{}) string {
141149
// ObjToKey returns a string namespace/name key for an object
142150
type ObjToKey func(interface{}) string
143151

152+
type ObjToKeys func(interface{}) []string
153+
154+
type ObjToReconcileKey func(interface{}) types.ReconcileKey
155+
156+
type ObjToReconcileKeys func(interface{}) []types.ReconcileKey
157+
144158
// MapToSelf returns the namespace/name key of obj
145159
func MapToSelf(obj interface{}) string {
146160
if key, err := cache.MetaNamespaceKeyFunc(obj); err != nil {

pkg/controller/example_watchandmap_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,135 @@ func ExampleGenericController_WatchTransformationOf() {
7171
// One time for program
7272
controller.RunInformersAndControllers(run.CreateRunArguments())
7373
}
74+
75+
func ExampleGenericController_WatchTransformationsOf() {
76+
// One time setup for program
77+
flag.Parse()
78+
informerFactory := config.GetKubernetesInformersOrDie()
79+
if err := controller.AddInformerProvider(&corev1.Pod{}, informerFactory.Core().V1().Pods()); err != nil {
80+
log.Fatalf("Could not set informer %v", err)
81+
}
82+
if err := controller.AddInformerProvider(&appsv1.ReplicaSet{}, informerFactory.Apps().V1().ReplicaSets()); err != nil {
83+
log.Fatalf("Could not set informer %v", err)
84+
}
85+
86+
// Per-controller setup
87+
c := &controller.GenericController{
88+
Reconcile: func(key types.ReconcileKey) error {
89+
fmt.Printf("Reconciling Pod %s\n", key)
90+
return nil
91+
},
92+
}
93+
err := c.Watch(&appsv1.ReplicaSet{})
94+
if err != nil {
95+
log.Fatalf("%v", err)
96+
}
97+
err = c.WatchTransformationsOf(&corev1.Pod{},
98+
func(i interface{}) []string {
99+
p, ok := i.(*corev1.Pod)
100+
if !ok {
101+
return []string{}
102+
}
103+
104+
// Find multiple parents based off the name
105+
return []string{
106+
p.Namespace + "/" + strings.Split(p.Name, "-")[0] + "-parent-1",
107+
p.Namespace + "/" + strings.Split(p.Name, "-")[0] + "-parent-2",
108+
}
109+
},
110+
)
111+
if err != nil {
112+
log.Fatalf("%v", err)
113+
}
114+
controller.AddController(c)
115+
116+
// One time for program
117+
controller.RunInformersAndControllers(run.CreateRunArguments())
118+
}
119+
120+
func ExampleGenericController_WatchTransformationKeyOf() {
121+
// One time setup for program
122+
flag.Parse()
123+
informerFactory := config.GetKubernetesInformersOrDie()
124+
if err := controller.AddInformerProvider(&corev1.Pod{}, informerFactory.Core().V1().Pods()); err != nil {
125+
log.Fatalf("Could not set informer %v", err)
126+
}
127+
if err := controller.AddInformerProvider(&appsv1.ReplicaSet{}, informerFactory.Apps().V1().ReplicaSets()); err != nil {
128+
log.Fatalf("Could not set informer %v", err)
129+
}
130+
131+
// Per-controller setup
132+
c := &controller.GenericController{
133+
Reconcile: func(key types.ReconcileKey) error {
134+
fmt.Printf("Reconciling Pod %s\n", key)
135+
return nil
136+
},
137+
}
138+
err := c.Watch(&appsv1.ReplicaSet{})
139+
if err != nil {
140+
log.Fatalf("%v", err)
141+
}
142+
err = c.WatchTransformationKeyOf(&corev1.Pod{},
143+
func(i interface{}) types.ReconcileKey {
144+
p, ok := i.(*corev1.Pod)
145+
if !ok {
146+
return types.ReconcileKey{}
147+
}
148+
149+
// Find multiple parents based off the name
150+
return types.ReconcileKey{p.Namespace, strings.Split(p.Name, "-")[0]}
151+
},
152+
)
153+
if err != nil {
154+
log.Fatalf("%v", err)
155+
}
156+
controller.AddController(c)
157+
158+
// One time for program
159+
controller.RunInformersAndControllers(run.CreateRunArguments())
160+
}
161+
162+
func ExampleGenericController_WatchTransformationKeysOf() {
163+
// One time setup for program
164+
flag.Parse()
165+
informerFactory := config.GetKubernetesInformersOrDie()
166+
if err := controller.AddInformerProvider(&corev1.Pod{}, informerFactory.Core().V1().Pods()); err != nil {
167+
log.Fatalf("Could not set informer %v", err)
168+
}
169+
if err := controller.AddInformerProvider(&appsv1.ReplicaSet{}, informerFactory.Apps().V1().ReplicaSets()); err != nil {
170+
log.Fatalf("Could not set informer %v", err)
171+
}
172+
173+
// Per-controller setup
174+
c := &controller.GenericController{
175+
Reconcile: func(key types.ReconcileKey) error {
176+
fmt.Printf("Reconciling Pod %s\n", key)
177+
return nil
178+
},
179+
}
180+
err := c.Watch(&appsv1.ReplicaSet{})
181+
if err != nil {
182+
log.Fatalf("%v", err)
183+
}
184+
err = c.WatchTransformationKeysOf(&corev1.Pod{},
185+
func(i interface{}) []types.ReconcileKey {
186+
p, ok := i.(*corev1.Pod)
187+
if !ok {
188+
return []types.ReconcileKey{}
189+
}
190+
191+
// Find multiple parents based off the name
192+
return []types.ReconcileKey{
193+
{p.Namespace, strings.Split(p.Name, "-")[0] + "-parent-1"},
194+
{p.Namespace, strings.Split(p.Name, "-")[0] + "-parent-2"},
195+
}
196+
},
197+
)
198+
if err != nil {
199+
log.Fatalf("%v", err)
200+
}
201+
controller.AddController(c)
202+
203+
// One time for program
204+
controller.RunInformersAndControllers(run.CreateRunArguments())
205+
}

0 commit comments

Comments
 (0)