Skip to content

Commit f122712

Browse files
committed
add consumer bootstrapping
1 parent b68fa21 commit f122712

File tree

13 files changed

+175
-44
lines changed

13 files changed

+175
-44
lines changed

examples/kcp/Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,8 @@ bootstrap:
6060
export KUBECONFIG=./.test/kcp.kubeconfig
6161
@go run ./config/main.go
6262

63+
64+
run:
65+
export KUBECONFIG=./.test/kcp.kubeconfig
66+
kubectl ws use widgets
67+
@go run ./main.go
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright 2024 The KCP 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 consumers
18+
19+
import (
20+
"context"
21+
"embed"
22+
23+
"k8s.io/apimachinery/pkg/util/sets"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
"sigs.k8s.io/controller-runtime/pkg/log"
26+
27+
confighelpers "github.com/kcp-dev/controller-runtime/examples/kcp/config/helpers"
28+
)
29+
30+
//go:embed *.yaml
31+
var fs embed.FS
32+
33+
// Bootstrap creates resources in this package by continuously retrying the list.
34+
// This is blocking, i.e. it only returns (with error) when the context is closed or with nil when
35+
// the bootstrapping is successfully completed.
36+
func Bootstrap(
37+
ctx context.Context,
38+
client client.Client,
39+
batteriesIncluded sets.Set[string],
40+
) error {
41+
log := log.FromContext(ctx)
42+
43+
log.Info("Bootstrapping consumers workspaces")
44+
return confighelpers.Bootstrap(ctx, client, fs, batteriesIncluded)
45+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: tenancy.kcp.io/v1alpha1
2+
kind: Workspace
3+
metadata:
4+
name: consumer1
5+
annotations:
6+
bootstrap.kcp.io/create-only: "true"
7+
spec:
8+
type:
9+
name: widgets
10+
path: root
11+
location:
12+
selector:
13+
matchLabels:
14+
name: root
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: tenancy.kcp.io/v1alpha1
2+
kind: Workspace
3+
metadata:
4+
name: consumer2
5+
annotations:
6+
bootstrap.kcp.io/create-only: "true"
7+
spec:
8+
type:
9+
name: widgets
10+
path: root
11+
location:
12+
selector:
13+
matchLabels:
14+
name: root

examples/kcp/config/main.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,20 @@ package main
1919
import (
2020
"net/url"
2121

22-
"github.com/davecgh/go-spew/spew"
2322
apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
2423
corev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
2524
tenancyv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1"
2625
"k8s.io/apimachinery/pkg/runtime"
2726
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2827
"k8s.io/apimachinery/pkg/util/sets"
2928
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
29+
"k8s.io/client-go/rest"
3030
ctrl "sigs.k8s.io/controller-runtime"
3131
"sigs.k8s.io/controller-runtime/pkg/client"
3232
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3333

34-
widgets "github.com/kcp-dev/controller-runtime/examples/kcp/config/widgets"
34+
"github.com/kcp-dev/controller-runtime/examples/kcp/config/consumers"
35+
"github.com/kcp-dev/controller-runtime/examples/kcp/config/widgets"
3536
"github.com/kcp-dev/controller-runtime/examples/kcp/config/widgets/resources"
3637
"sigs.k8s.io/controller-runtime/pkg/log"
3738
)
@@ -75,34 +76,43 @@ func main() {
7576

7677
log := log.FromContext(ctx)
7778

78-
c, err := client.New(restConfig, client.Options{
79+
// Client for root cluster for workspace widget and consumers to bootstrap.
80+
clientRoot, err := client.New(restConfig, client.Options{
7981
Scheme: scheme,
8082
})
8183
if err != nil {
8284
log.Error(err, "unable to create client")
8385
}
84-
fakeBatteries := sets.New("")
85-
86-
err = widgets.Bootstrap(ctx, c, fakeBatteries)
87-
if err != nil {
88-
log.Error(err, "failed to bootstrap widgets")
89-
}
9086

9187
// Hack to set the clusterName in the restConfig.Host
92-
restConfig.Host = restConfig.Host + ":" + url.PathEscape(clusterName)
88+
restWidgetsConfig := rest.CopyConfig(restConfig)
89+
restConfig.Host = restWidgetsConfig.Host + ":" + url.PathEscape(clusterName)
9390
if err != nil {
9491
log.Error(err, "unable to set clusterName")
9592
}
96-
spew.Dump(restConfig.Host)
97-
c, err = client.New(restConfig, client.Options{
93+
94+
clientWidgets, err := client.New(restConfig, client.Options{
9895
Scheme: scheme,
9996
})
10097
if err != nil {
10198
log.Error(err, "unable to create client")
10299
}
103100

104-
err = resources.Bootstrap(ctx, c, fakeBatteries)
101+
fakeBatteries := sets.New("")
102+
103+
err = widgets.Bootstrap(ctx, clientRoot, fakeBatteries)
104+
if err != nil {
105+
log.Error(err, "failed to bootstrap widgets")
106+
}
107+
108+
err = resources.Bootstrap(ctx, clientWidgets, fakeBatteries)
105109
if err != nil {
106110
log.Error(err, "failed to bootstrap resources")
107111
}
112+
113+
err = consumers.Bootstrap(ctx, clientRoot, fakeBatteries)
114+
if err != nil {
115+
log.Error(err, "failed to bootstrap consumers")
116+
}
117+
108118
}

examples/kcp/config/widgets/bootstrap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package widdgets
17+
package widgets
1818

1919
import (
2020
"context"

examples/kcp/config/widgets/resources/apiexport-data.my.domain.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@ metadata:
66
spec:
77
latestResourceSchemas:
88
- v240406-90e42b7b.widgets.data.my.domain
9+
permissionClaims:
10+
- all: true
11+
resource: configmaps
12+
- all: true
13+
resource: secrets
914
status: {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: tenancy.kcp.io/v1alpha1
2+
kind: WorkspaceType
3+
metadata:
4+
name: widgets
5+
spec:
6+
extend:
7+
with:
8+
- name: universal
9+
path: root
10+
defaultAPIBindings:
11+
- path: root:widgets
12+
export: data.my.domain

examples/kcp/controllers/configmap_controller.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ import (
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/apimachinery/pkg/types"
2828

29-
"github.com/kcp-dev/logicalcluster/v3"
30-
3129
ctrl "sigs.k8s.io/controller-runtime"
3230
"sigs.k8s.io/controller-runtime/pkg/client"
3331
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
32+
"sigs.k8s.io/controller-runtime/pkg/kontext"
3433
"sigs.k8s.io/controller-runtime/pkg/log"
3534
)
3635

@@ -41,12 +40,9 @@ type ConfigMapReconciler struct {
4140
func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
4241
log := log.FromContext(ctx).WithValues("cluster", req.ClusterName)
4342

44-
ctx = logicalcluster.WithCluster(ctx, logicalcluster.New(req.ClusterName))
45-
4643
// Test get
4744
var configMap corev1.ConfigMap
48-
49-
if err := r.Get(ctx, req.NamespacedName, &configMap); err != nil {
45+
if err := r.Client.Get(ctx, req.NamespacedName, &configMap); err != nil {
5046
log.Error(err, "unable to get configmap")
5147
return ctrl.Result{}, nil
5248
}
@@ -61,7 +57,7 @@ func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
6157
labels["response"] = response
6258

6359
// Test Update
64-
if err := r.Update(ctx, &configMap); err != nil {
60+
if err := r.Client.Update(ctx, &configMap); err != nil {
6561
return ctrl.Result{}, err
6662
}
6763
log.Info("Update: updated configMap")
@@ -71,15 +67,16 @@ func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
7167

7268
// Test list
7369
var configMapList corev1.ConfigMapList
74-
if err := r.List(ctx, &configMapList); err != nil {
70+
if err := r.Client.List(ctx, &configMapList); err != nil {
7571
log.Error(err, "unable to list configmaps")
7672
return ctrl.Result{}, nil
7773
}
7874
log.Info("List: got", "itemCount", len(configMapList.Items))
7975
found := false
8076
for _, cm := range configMapList.Items {
81-
if !logicalcluster.From(&cm).Empty() {
82-
log.Info("List: got", "clusterName", logicalcluster.From(&cm).String(), "namespace", cm.Namespace, "name", cm.Name)
77+
cluster, ok := kontext.ClusterFrom(ctx)
78+
if !ok {
79+
log.Info("List: got", "clusterName", cluster.String(), "namespace", cm.Namespace, "name", cm.Name)
8380
} else {
8481
if cm.Name == configMap.Name && cm.Namespace == configMap.Namespace {
8582
if found {
@@ -97,15 +94,15 @@ func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
9794
var namespace corev1.Namespace
9895
nsKey := types.NamespacedName{Name: nsName}
9996

100-
if err := r.Get(ctx, nsKey, &namespace); err != nil {
97+
if err := r.Client.Get(ctx, nsKey, &namespace); err != nil {
10198
if !apierrors.IsNotFound(err) {
10299
log.Error(err, "unable to get namespace")
103100
return ctrl.Result{}, err
104101
}
105102

106103
// Need to create ns
107104
namespace.SetName(nsName)
108-
if err = r.Create(ctx, &namespace); err != nil {
105+
if err = r.Client.Create(ctx, &namespace); err != nil {
109106
log.Error(err, "unable to create namespace")
110107
return ctrl.Result{}, err
111108
}
@@ -150,5 +147,5 @@ func (r *ConfigMapReconciler) SetupWithManager(mgr ctrl.Manager) error {
150147
return ctrl.NewControllerManagedBy(mgr).
151148
For(&corev1.ConfigMap{}).
152149
Owns(&corev1.Secret{}).
153-
Complete(r)
150+
Complete(WithClusterInContext(r))
154151
}

examples/kcp/controllers/helper.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2024 The KCP 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 controllers
18+
19+
import (
20+
"context"
21+
22+
"github.com/kcp-dev/logicalcluster/v3"
23+
"sigs.k8s.io/controller-runtime/pkg/kontext"
24+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
25+
)
26+
27+
// WithClusterInContext injects a cluster name into a context such that
28+
// cluster clients and cache work out of the box.
29+
func WithClusterInContext(r reconcile.Reconciler) reconcile.Reconciler {
30+
return reconcile.Func(func(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
31+
ctx = kontext.WithCluster(ctx, logicalcluster.Name(req.ClusterName))
32+
return r.Reconcile(ctx, req)
33+
})
34+
}

examples/kcp/controllers/widget_controller.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package controllers
1919
import (
2020
"context"
2121

22-
"github.com/kcp-dev/logicalcluster/v3"
2322
"k8s.io/apimachinery/pkg/api/errors"
2423
"k8s.io/apimachinery/pkg/runtime"
2524
ctrl "sigs.k8s.io/controller-runtime"
@@ -45,18 +44,15 @@ func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
4544

4645
// You probably wouldn't need to do this, but if you wanted to list all instances across all logical clusters:
4746
var allWidgets datav1alpha1.WidgetList
48-
if err := r.List(ctx, &allWidgets); err != nil {
47+
if err := r.Client.List(ctx, &allWidgets); err != nil {
4948
return ctrl.Result{}, err
5049
}
5150

5251
logger.Info("Listed all widgets across all workspaces", "count", len(allWidgets.Items))
5352

54-
// Add the logical cluster to the context
55-
ctx = logicalcluster.WithCluster(ctx, logicalcluster.New(req.ClusterName))
56-
5753
logger.Info("Getting widget")
58-
var widget datav1alpha1.Widget
59-
if err := r.Get(ctx, req.NamespacedName, &widget); err != nil {
54+
var w datav1alpha1.Widget
55+
if err := r.Client.Get(ctx, req.NamespacedName, &w); err != nil {
6056
if errors.IsNotFound(err) {
6157
// Normal - was deleted
6258
return ctrl.Result{}, nil
@@ -67,22 +63,23 @@ func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
6763

6864
logger.Info("Listing all widgets in the current logical cluster")
6965
var list datav1alpha1.WidgetList
70-
if err := r.List(ctx, &list); err != nil {
66+
if err := r.Client.List(ctx, &list); err != nil {
7167
return ctrl.Result{}, err
7268
}
7369

7470
numWidgets := len(list.Items)
75-
if numWidgets == widget.Status.Total {
71+
if numWidgets == w.Status.Total {
7672
logger.Info("No need to patch because the widget status is already correct")
7773
return ctrl.Result{}, nil
7874
}
7975

8076
logger.Info("Patching widget status to store total widget count in the current logical cluster")
81-
widgetCopy := widget.DeepCopy()
77+
original := w.DeepCopy()
78+
patch := client.MergeFrom(original)
8279

83-
widgetCopy.Status.Total = numWidgets
80+
w.Status.Total = numWidgets
8481

85-
if err := r.Status().Patch(ctx, widgetCopy, patch); err != nil {
82+
if err := r.Client.Status().Patch(ctx, &w, patch); err != nil {
8683
return ctrl.Result{}, err
8784
}
8885

@@ -93,5 +90,5 @@ func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
9390
func (r *WidgetReconciler) SetupWithManager(mgr ctrl.Manager) error {
9491
return ctrl.NewControllerManagedBy(mgr).
9592
For(&datav1alpha1.Widget{}).
96-
Complete(r)
93+
Complete(WithClusterInContext(r))
9794
}

0 commit comments

Comments
 (0)