Skip to content

Commit 123d53c

Browse files
committed
✨ Cluster Provider and cluster-aware controllers
Signed-off-by: Vince Prignano <[email protected]>
1 parent 0d8b064 commit 123d53c

File tree

25 files changed

+1923
-88
lines changed

25 files changed

+1923
-88
lines changed

.github/workflows/golangci-lint.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ jobs:
1919
go-version: '1.20'
2020
cache: false
2121
- uses: actions/checkout@v3
22+
- name: Set up Go
23+
uses: actions/[email protected]
24+
with:
25+
go-version: "1.20"
26+
check-latest: true
2227
- name: golangci-lint
2328
uses: golangci/golangci-lint-action@v3
2429
with:

examples/fleet/go.mod

Lines changed: 75 additions & 0 deletions
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.19
4+
5+
replace sigs.k8s.io/controller-runtime => ../..
6+
7+
require (
8+
k8s.io/api v0.26.1
9+
k8s.io/apimachinery v0.26.1
10+
k8s.io/client-go v0.26.1
11+
k8s.io/klog/v2 v2.90.0
12+
sigs.k8s.io/controller-runtime v0.0.0-00010101000000-000000000000
13+
sigs.k8s.io/kind v0.17.0
14+
sigs.k8s.io/logical-cluster v0.0.1-alpha.0
15+
)
16+
17+
require (
18+
github.com/BurntSushi/toml v1.0.0 // indirect
19+
github.com/alessio/shellescape v1.4.1 // indirect
20+
github.com/beorn7/perks v1.0.1 // indirect
21+
github.com/cespare/xxhash/v2 v2.1.2 // indirect
22+
github.com/davecgh/go-spew v1.1.1 // indirect
23+
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
24+
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
25+
github.com/fsnotify/fsnotify v1.6.0 // indirect
26+
github.com/go-logr/logr v1.2.3 // indirect
27+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
28+
github.com/go-openapi/jsonreference v0.20.0 // indirect
29+
github.com/go-openapi/swag v0.19.14 // indirect
30+
github.com/gogo/protobuf v1.3.2 // indirect
31+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
32+
github.com/golang/protobuf v1.5.2 // indirect
33+
github.com/google/gnostic v0.5.7-v3refs // indirect
34+
github.com/google/go-cmp v0.5.9 // indirect
35+
github.com/google/gofuzz v1.1.0 // indirect
36+
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
37+
github.com/google/uuid v1.1.2 // indirect
38+
github.com/imdario/mergo v0.3.6 // indirect
39+
github.com/inconshreveable/mousetrap v1.0.1 // indirect
40+
github.com/josharian/intern v1.0.0 // indirect
41+
github.com/json-iterator/go v1.1.12 // indirect
42+
github.com/mailru/easyjson v0.7.6 // indirect
43+
github.com/mattn/go-isatty v0.0.14 // indirect
44+
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
45+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
46+
github.com/modern-go/reflect2 v1.0.2 // indirect
47+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
48+
github.com/pelletier/go-toml v1.9.4 // indirect
49+
github.com/pkg/errors v0.9.1 // indirect
50+
github.com/prometheus/client_golang v1.14.0 // indirect
51+
github.com/prometheus/client_model v0.3.0 // indirect
52+
github.com/prometheus/common v0.37.0 // indirect
53+
github.com/prometheus/procfs v0.8.0 // indirect
54+
github.com/spf13/cobra v1.6.0 // indirect
55+
github.com/spf13/pflag v1.0.5 // indirect
56+
golang.org/x/net v0.7.0 // indirect
57+
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
58+
golang.org/x/sys v0.5.0 // indirect
59+
golang.org/x/term v0.5.0 // indirect
60+
golang.org/x/text v0.7.0 // indirect
61+
golang.org/x/time v0.3.0 // indirect
62+
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
63+
google.golang.org/appengine v1.6.7 // indirect
64+
google.golang.org/protobuf v1.28.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.26.1 // indirect
69+
k8s.io/component-base v0.26.1 // indirect
70+
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
71+
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
72+
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
73+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
74+
sigs.k8s.io/yaml v1.3.0 // indirect
75+
)

examples/fleet/go.sum

Lines changed: 627 additions & 0 deletions
Large diffs are not rendered by default.

examples/fleet/main.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"strings"
7+
"sync"
8+
"time"
9+
10+
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/util/sets"
12+
"k8s.io/apimachinery/pkg/watch"
13+
"k8s.io/client-go/tools/clientcmd"
14+
"k8s.io/klog/v2"
15+
ctrl "sigs.k8s.io/controller-runtime"
16+
"sigs.k8s.io/controller-runtime/pkg/builder"
17+
"sigs.k8s.io/controller-runtime/pkg/cluster"
18+
"sigs.k8s.io/controller-runtime/pkg/envtest"
19+
"sigs.k8s.io/controller-runtime/pkg/log"
20+
"sigs.k8s.io/controller-runtime/pkg/manager"
21+
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
22+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
23+
kind "sigs.k8s.io/kind/pkg/cluster"
24+
"sigs.k8s.io/logical-cluster"
25+
)
26+
27+
func init() {
28+
ctrl.SetLogger(klog.Background())
29+
}
30+
31+
func main() {
32+
entryLog := log.Log.WithName("entrypoint")
33+
34+
testEnv := &envtest.Environment{}
35+
cfg, err := testEnv.Start()
36+
if err != nil {
37+
entryLog.Error(err, "failed to start local environment")
38+
os.Exit(1)
39+
}
40+
defer func() {
41+
if testEnv == nil {
42+
return
43+
}
44+
if err := testEnv.Stop(); err != nil {
45+
entryLog.Error(err, "failed to stop local environment")
46+
os.Exit(1)
47+
}
48+
}()
49+
50+
// Setup a Manager
51+
entryLog.Info("Setting up manager")
52+
mgr, err := manager.New(
53+
cfg,
54+
manager.Options{}.WithExperimentalClusterProvider(&KindClusterProvider{}),
55+
)
56+
if err != nil {
57+
entryLog.Error(err, "unable to set up overall controller manager")
58+
os.Exit(1)
59+
}
60+
61+
builder.ControllerManagedBy(mgr).
62+
For(&corev1.Pod{}).Complete(reconcile.Func(
63+
func(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
64+
log := log.FromContext(ctx)
65+
66+
cluster, err := mgr.GetCluster(ctx, req.Cluster)
67+
if err != nil {
68+
return reconcile.Result{}, err
69+
}
70+
client := cluster.GetClient()
71+
72+
// Retrieve the pod from the cluster.
73+
pod := &corev1.Pod{}
74+
if err := client.Get(ctx, req.NamespacedName, pod); err != nil {
75+
return reconcile.Result{}, err
76+
}
77+
log.Info("Reconciling pod", "name", pod.Name, "uuid", pod.UID)
78+
79+
// Print any annotations that start with fleet.
80+
for k, v := range pod.Labels {
81+
if strings.HasPrefix(k, "fleet-") {
82+
log.Info("Detected fleet annotation!", "key", k, "value", v)
83+
}
84+
}
85+
86+
return ctrl.Result{}, nil
87+
},
88+
))
89+
90+
entryLog.Info("Starting manager")
91+
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
92+
entryLog.Error(err, "unable to run manager")
93+
os.Exit(1)
94+
}
95+
}
96+
97+
// KindClusterProvider is a cluster provider that works with a local Kind instance.
98+
type KindClusterProvider struct{}
99+
100+
func (k *KindClusterProvider) Get(ctx context.Context, name logical.Name, opts ...cluster.Option) (cluster.Cluster, error) {
101+
provider := kind.NewProvider()
102+
kubeconfig, err := provider.KubeConfig(string(name), false)
103+
if err != nil {
104+
return nil, err
105+
}
106+
// Parse the kubeconfig into a rest.Config.
107+
cfg, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig))
108+
if err != nil {
109+
return nil, err
110+
}
111+
return cluster.New(cfg, opts...)
112+
}
113+
114+
func (k *KindClusterProvider) List() ([]logical.Name, error) {
115+
provider := kind.NewProvider()
116+
list, err := provider.List()
117+
if err != nil {
118+
return nil, err
119+
}
120+
res := make([]logical.Name, 0, len(list))
121+
for _, cluster := range list {
122+
if !strings.HasPrefix(cluster, "fleet-") {
123+
continue
124+
}
125+
res = append(res, logical.Name(cluster))
126+
}
127+
return res, nil
128+
}
129+
130+
func (k *KindClusterProvider) Watch() (cluster.Watcher, error) {
131+
return &KindWatcher{ch: make(chan cluster.WatchEvent)}, nil
132+
}
133+
134+
type KindWatcher struct {
135+
init sync.Once
136+
wg sync.WaitGroup
137+
ch chan cluster.WatchEvent
138+
cancel context.CancelFunc
139+
}
140+
141+
func (k *KindWatcher) Stop() {
142+
if k.cancel != nil {
143+
k.cancel()
144+
}
145+
k.wg.Wait()
146+
close(k.ch)
147+
}
148+
func (k *KindWatcher) ResultChan() <-chan cluster.WatchEvent {
149+
k.init.Do(func() {
150+
ctx, cancel := context.WithCancel(context.Background())
151+
k.cancel = cancel
152+
set := sets.New[string]()
153+
k.wg.Add(1)
154+
go func() {
155+
defer k.wg.Done()
156+
for {
157+
select {
158+
case <-time.After(2 * time.Second):
159+
provider := kind.NewProvider()
160+
list, err := provider.List()
161+
if err != nil {
162+
klog.Error(err)
163+
continue
164+
}
165+
newSet := sets.New(list...)
166+
// Check for new clusters.
167+
for _, cl := range newSet.Difference(set).UnsortedList() {
168+
if !strings.HasPrefix(cl, "fleet-") {
169+
continue
170+
}
171+
k.ch <- cl.WatchEvent{
172+
Type: watch.Added,
173+
Name: logical.Name(cl),
174+
}
175+
}
176+
// Check for deleted clusters.
177+
for _, cl := range set.Difference(newSet).UnsortedList() {
178+
if !strings.HasPrefix(cl, "fleet-") {
179+
continue
180+
}
181+
k.ch <- cluster.WatchEvent{
182+
Type: watch.Deleted,
183+
Name: logical.Name(cl),
184+
}
185+
}
186+
set = newSet
187+
case <-ctx.Done():
188+
return
189+
}
190+
}
191+
}()
192+
})
193+
return k.ch
194+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
k8s.io/component-base v0.27.1
2424
k8s.io/klog/v2 v2.90.1
2525
k8s.io/utils v0.0.0-20230209194617-a36077c30491
26+
sigs.k8s.io/logical-cluster v0.0.1-alpha.0
2627
sigs.k8s.io/yaml v1.3.0
2728
)
2829

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPB
278278
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
279279
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
280280
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
281+
sigs.k8s.io/logical-cluster v0.0.1-alpha.0 h1:vigMG0I1fgDVn0hsTOeZB55AmplXC7D4iLa60qeyX70=
282+
sigs.k8s.io/logical-cluster v0.0.1-alpha.0/go.mod h1:7YymTkuUFI+tkwCRPMsk+TiyBQiPDKRArxVAAGpezZI=
281283
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
282284
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
283285
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=

0 commit comments

Comments
 (0)