Skip to content

Commit 191faea

Browse files
embikvincepristttsivanape
committed
Add fleet example for multi-cluster support
On-behalf-of: SAP [email protected] Co-authored-by: Vince Prignano <[email protected]> Co-authored-by: Dr. Stefan Schimanski <[email protected]> Co-authored-by: Iván Álvarez <[email protected]> Signed-off-by: Marvin Beckers <[email protected]>
1 parent 5f3bf93 commit 191faea

File tree

5 files changed

+764
-0
lines changed

5 files changed

+764
-0
lines changed

examples/fleet/cluster.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/types"
21+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
22+
)
23+
24+
type clusterRequest struct {
25+
reconcile.Request
26+
ClusterName string
27+
}
28+
29+
// String returns the general purpose string representation.
30+
func (cr *clusterRequest) String() string {
31+
if cr.ClusterName == "" {
32+
return cr.NamespacedName.String()
33+
}
34+
return "cluster://" + cr.ClusterName + string(types.Separator) + cr.NamespacedName.String()
35+
}

examples/fleet/eventhandler.go

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"reflect"
22+
23+
"k8s.io/apimachinery/pkg/types"
24+
"k8s.io/client-go/util/workqueue"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
"sigs.k8s.io/controller-runtime/pkg/cluster"
27+
"sigs.k8s.io/controller-runtime/pkg/event"
28+
"sigs.k8s.io/controller-runtime/pkg/handler"
29+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
30+
)
31+
32+
func wrapHandler[request comparable](handler handler.TypedEventHandler[client.Object, request], cluster cluster.Cluster) handler.TypedEventHandler[client.Object, request] {
33+
return &wrappedEventHandler[request]{
34+
handler: handler,
35+
cluster: cluster,
36+
}
37+
}
38+
39+
type wrappedEventHandler[request comparable] struct {
40+
handler handler.TypedEventHandler[client.Object, request]
41+
cluster cluster.Cluster
42+
}
43+
44+
func (w *wrappedEventHandler[request]) Create(ctx context.Context, e event.TypedCreateEvent[client.Object], q workqueue.TypedRateLimitingInterface[request]) {
45+
annotations := e.Object.GetAnnotations()
46+
if annotations == nil {
47+
annotations = make(map[string]string)
48+
}
49+
annotations["cluster-name"] = w.cluster.Name()
50+
e.Object.SetAnnotations(annotations)
51+
w.handler.Create(ctx, e, q)
52+
}
53+
54+
func (w *wrappedEventHandler[request]) Update(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[request]) {
55+
annotations := e.ObjectNew.GetAnnotations()
56+
if annotations == nil {
57+
annotations = make(map[string]string)
58+
}
59+
annotations["cluster-name"] = w.cluster.Name()
60+
e.ObjectNew.SetAnnotations(annotations)
61+
w.handler.Update(ctx, e, q)
62+
}
63+
64+
func (w *wrappedEventHandler[request]) Delete(ctx context.Context, e event.TypedDeleteEvent[client.Object], q workqueue.TypedRateLimitingInterface[request]) {
65+
annotations := e.Object.GetAnnotations()
66+
if annotations == nil {
67+
annotations = make(map[string]string)
68+
}
69+
annotations["cluster-name"] = w.cluster.Name()
70+
e.Object.SetAnnotations(annotations)
71+
w.handler.Delete(ctx, e, q)
72+
}
73+
74+
func (w *wrappedEventHandler[request]) Generic(ctx context.Context, e event.TypedGenericEvent[client.Object], q workqueue.TypedRateLimitingInterface[request]) {
75+
annotations := e.Object.GetAnnotations()
76+
if annotations == nil {
77+
annotations = make(map[string]string)
78+
}
79+
annotations["cluster-name"] = w.cluster.Name()
80+
e.Object.SetAnnotations(annotations)
81+
w.handler.Generic(ctx, e, q)
82+
}
83+
84+
var _ handler.TypedEventHandler[client.Object, clusterRequest] = &EnqueueClusterRequestForObject{}
85+
86+
type EnqueueClusterRequestForObject = TypedEnqueueClusterRequestForObject[client.Object]
87+
88+
type TypedEnqueueClusterRequestForObject[object client.Object] struct{}
89+
90+
// Create implements EventHandler.
91+
func (e *TypedEnqueueClusterRequestForObject[T]) Create(ctx context.Context, evt event.TypedCreateEvent[T], q workqueue.TypedRateLimitingInterface[clusterRequest]) {
92+
if isNil(evt.Object) {
93+
return
94+
}
95+
96+
clusterName := evt.Object.GetAnnotations()["cluster-name"]
97+
98+
q.Add(clusterRequest{
99+
ClusterName: clusterName,
100+
Request: reconcile.Request{NamespacedName: types.NamespacedName{
101+
Name: evt.Object.GetName(),
102+
Namespace: evt.Object.GetNamespace(),
103+
}}})
104+
}
105+
106+
// Update implements EventHandler.
107+
func (e *TypedEnqueueClusterRequestForObject[T]) Update(ctx context.Context, evt event.TypedUpdateEvent[T], q workqueue.TypedRateLimitingInterface[clusterRequest]) {
108+
clusterName := evt.ObjectNew.GetAnnotations()["cluster-name"]
109+
110+
switch {
111+
case !isNil(evt.ObjectNew):
112+
q.Add(clusterRequest{
113+
ClusterName: clusterName,
114+
Request: reconcile.Request{NamespacedName: types.NamespacedName{
115+
Name: evt.ObjectNew.GetName(),
116+
Namespace: evt.ObjectNew.GetNamespace(),
117+
}}})
118+
case !isNil(evt.ObjectOld):
119+
q.Add(clusterRequest{
120+
ClusterName: clusterName,
121+
Request: reconcile.Request{NamespacedName: types.NamespacedName{
122+
Name: evt.ObjectOld.GetName(),
123+
Namespace: evt.ObjectOld.GetNamespace(),
124+
}}})
125+
}
126+
}
127+
128+
// Delete implements EventHandler.
129+
func (e *TypedEnqueueClusterRequestForObject[T]) Delete(ctx context.Context, evt event.TypedDeleteEvent[T], q workqueue.TypedRateLimitingInterface[clusterRequest]) {
130+
if isNil(evt.Object) {
131+
return
132+
}
133+
134+
clusterName := evt.Object.GetAnnotations()["cluster-name"]
135+
136+
q.Add(clusterRequest{
137+
ClusterName: clusterName,
138+
Request: reconcile.Request{NamespacedName: types.NamespacedName{
139+
Name: evt.Object.GetName(),
140+
Namespace: evt.Object.GetNamespace(),
141+
}}})
142+
}
143+
144+
// Generic implements EventHandler.
145+
func (e *TypedEnqueueClusterRequestForObject[T]) Generic(ctx context.Context, evt event.TypedGenericEvent[T], q workqueue.TypedRateLimitingInterface[clusterRequest]) {
146+
if isNil(evt.Object) {
147+
return
148+
}
149+
150+
clusterName := evt.Object.GetAnnotations()["cluster-name"]
151+
152+
q.Add(clusterRequest{
153+
ClusterName: clusterName,
154+
Request: reconcile.Request{NamespacedName: types.NamespacedName{
155+
Name: evt.Object.GetName(),
156+
Namespace: evt.Object.GetNamespace(),
157+
}}})
158+
}
159+
160+
func isNil(arg any) bool {
161+
if v := reflect.ValueOf(arg); !v.IsValid() || ((v.Kind() == reflect.Ptr ||
162+
v.Kind() == reflect.Interface ||
163+
v.Kind() == reflect.Slice ||
164+
v.Kind() == reflect.Map ||
165+
v.Kind() == reflect.Chan ||
166+
v.Kind() == reflect.Func) && v.IsNil()) {
167+
return true
168+
}
169+
return false
170+
}

examples/fleet/go.mod

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
module sigs.k8s.io/controller-runtime/examples/fleet
2+
3+
go 1.23.2
4+
5+
replace sigs.k8s.io/controller-runtime => ../..
6+
7+
require (
8+
github.com/go-logr/logr v1.4.2
9+
k8s.io/api v0.32.0-beta.0
10+
k8s.io/apimachinery v0.32.0-beta.0
11+
k8s.io/client-go v0.32.0-beta.0
12+
sigs.k8s.io/controller-runtime v0.0.0-00010101000000-000000000000
13+
sigs.k8s.io/kind v0.24.0
14+
)
15+
16+
require (
17+
github.com/BurntSushi/toml v1.4.0 // indirect
18+
github.com/alessio/shellescape v1.4.2 // indirect
19+
github.com/beorn7/perks v1.0.1 // indirect
20+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
21+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
22+
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
23+
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
24+
github.com/fsnotify/fsnotify v1.7.0 // indirect
25+
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
26+
github.com/go-logr/zapr v1.3.0 // indirect
27+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
28+
github.com/go-openapi/jsonreference v0.20.2 // indirect
29+
github.com/go-openapi/swag v0.23.0 // indirect
30+
github.com/gogo/protobuf v1.3.2 // indirect
31+
github.com/golang/protobuf v1.5.4 // indirect
32+
github.com/google/gnostic-models v0.6.8 // indirect
33+
github.com/google/go-cmp v0.6.0 // indirect
34+
github.com/google/gofuzz v1.2.0 // indirect
35+
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
36+
github.com/google/uuid v1.6.0 // indirect
37+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
38+
github.com/josharian/intern v1.0.0 // indirect
39+
github.com/json-iterator/go v1.1.12 // indirect
40+
github.com/mailru/easyjson v0.7.7 // indirect
41+
github.com/mattn/go-isatty v0.0.20 // indirect
42+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
43+
github.com/modern-go/reflect2 v1.0.2 // indirect
44+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
45+
github.com/pelletier/go-toml v1.9.5 // indirect
46+
github.com/pkg/errors v0.9.1 // indirect
47+
github.com/prometheus/client_golang v1.19.1 // indirect
48+
github.com/prometheus/client_model v0.6.1 // indirect
49+
github.com/prometheus/common v0.55.0 // indirect
50+
github.com/prometheus/procfs v0.15.1 // indirect
51+
github.com/spf13/cobra v1.8.1 // indirect
52+
github.com/spf13/pflag v1.0.5 // indirect
53+
github.com/x448/float16 v0.8.4 // indirect
54+
go.uber.org/multierr v1.11.0 // indirect
55+
go.uber.org/zap v1.27.0 // indirect
56+
golang.org/x/net v0.30.0 // indirect
57+
golang.org/x/oauth2 v0.23.0 // indirect
58+
golang.org/x/sync v0.8.0 // indirect
59+
golang.org/x/sys v0.26.0 // indirect
60+
golang.org/x/term v0.25.0 // indirect
61+
golang.org/x/text v0.19.0 // indirect
62+
golang.org/x/time v0.7.0 // indirect
63+
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
64+
google.golang.org/protobuf v1.35.1 // indirect
65+
gopkg.in/inf.v0 v0.9.1 // indirect
66+
gopkg.in/yaml.v2 v2.4.0 // indirect
67+
gopkg.in/yaml.v3 v3.0.1 // indirect
68+
k8s.io/apiextensions-apiserver v0.32.0-beta.0 // indirect
69+
k8s.io/klog/v2 v2.130.1 // indirect
70+
k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 // indirect
71+
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
72+
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
73+
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
74+
sigs.k8s.io/yaml v1.4.0 // indirect
75+
)

0 commit comments

Comments
 (0)