Skip to content

Commit fe0fc0d

Browse files
committed
docs: code-sample for workspace initializer
On-behalf-of: SAP <simon.bein@sap.com> Signed-off-by: Simon Bein <simontheleg@gmail.com>
1 parent b51d1bf commit fe0fc0d

File tree

1 file changed

+153
-4
lines changed

1 file changed

+153
-4
lines changed

docs/content/concepts/workspaces/workspace-initialization.md

Lines changed: 153 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,156 @@ You can use this url to construct a kubeconfig for your controller. To do so, us
9595

9696
### Code Sample
9797

98-
* It is important to use the kcp-dev controller runtime fork, as regular controller runtime is not able to deal with all logical clusters being name "cluster"
99-
* LogicalClusters cannot updated using update api, but must be updated using patch api
100-
101-
// TODO paste in sample once it is finished
98+
When writing a custom initializer, the following needs to be taken into account:
99+
100+
* You need to use the kcp-dev controller-runtime fork, as regular controller-runtime is not able to work as under the hood all LogicalClusters have the sam name
101+
* You need to update LogicalClusters using patches; They cannot be updated using the update api
102+
103+
Keeping this in mind, you can use the following example as a starting point for your intitialization controller
104+
105+
=== "main.go"
106+
107+
```Go
108+
package main
109+
110+
import (
111+
"context"
112+
"fmt"
113+
"log/slog"
114+
"os"
115+
"slices"
116+
"strings"
117+
118+
"github.com/go-logr/logr"
119+
kcpcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
120+
"github.com/kcp-dev/kcp/sdk/apis/tenancy/initialization"
121+
"k8s.io/client-go/tools/clientcmd"
122+
ctrl "sigs.k8s.io/controller-runtime"
123+
"sigs.k8s.io/controller-runtime/pkg/client"
124+
"sigs.k8s.io/controller-runtime/pkg/kcp"
125+
"sigs.k8s.io/controller-runtime/pkg/manager"
126+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
127+
)
128+
129+
type Reconciler struct {
130+
Client client.Client
131+
Log logr.Logger
132+
InitializerName kcpcorev1alpha1.LogicalClusterInitializer
133+
}
134+
135+
func main() {
136+
if err := execute(); err != nil {
137+
fmt.Println(err)
138+
os.Exit(1)
139+
}
140+
}
141+
142+
func execute() error {
143+
kubeconfigpath := "<path-to-kubeconfig>"
144+
145+
config, err := clientcmd.BuildConfigFromFlags("", kubeconfigpath)
146+
if err != nil {
147+
return err
148+
}
149+
150+
logger := logr.FromSlogHandler(slog.NewTextHandler(os.Stderr, nil))
151+
ctrl.SetLogger(logger)
152+
153+
mgr, err := kcp.NewClusterAwareManager(config, manager.Options{
154+
Logger: logger,
155+
})
156+
if err != nil {
157+
return err
158+
}
159+
if err := kcpcorev1alpha1.AddToScheme(mgr.GetScheme()); err != nil {
160+
return err
161+
}
162+
163+
// since the initializers name is is the last part of the hostname, we can take it from there
164+
initializerName := config.Host[strings.LastIndex(config.Host, "/")+1:]
165+
166+
r := Reconciler{
167+
Client: mgr.GetClient(),
168+
Log: mgr.GetLogger().WithName("initializer-controller"),
169+
InitializerName: kcpcorev1alpha1.LogicalClusterInitializer(initializerName),
170+
}
171+
172+
if err := r.SetupWithManager(mgr); err != nil {
173+
return err
174+
}
175+
mgr.GetLogger().Info("Setup complete")
176+
177+
if err := mgr.Start(context.Background()); err != nil {
178+
return err
179+
}
180+
181+
return nil
182+
}
183+
184+
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
185+
return ctrl.NewControllerManagedBy(mgr).
186+
For(&kcpcorev1alpha1.LogicalCluster{}).
187+
// we need to use kcp.WithClusterInContext here to target the correct logical clusters during reconciliation
188+
Complete(kcp.WithClusterInContext(r))
189+
}
190+
191+
func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
192+
log := r.Log.WithValues("clustername", req.ClusterName)
193+
log.Info("Reconciling")
194+
195+
lc := &kcpcorev1alpha1.LogicalCluster{}
196+
if err := r.Client.Get(ctx, req.NamespacedName, lc); err != nil {
197+
return reconcile.Result{}, err
198+
}
199+
200+
// check if your initializer is still set on the logicalcluster
201+
if slices.Contains(lc.Status.Initializers, r.InitializerName) {
202+
203+
log.Info("Starting to initialize cluster")
204+
// your logic here to initialize a Workspace
205+
206+
// after your initialization is done, don't forget to remove your initializer
207+
// Since LogicalCluster objects cannot be directly updated, we need to create a patch.
208+
patch := client.MergeFrom(lc.DeepCopy())
209+
lc.Status.Initializers = initialization.EnsureInitializerAbsent(r.InitializerName, lc.Status.Initializers)
210+
if err := r.Client.Status().Patch(ctx, lc, patch); err != nil {
211+
return reconcile.Result{}, err
212+
}
213+
}
214+
215+
return reconcile.Result{}, nil
216+
}
217+
```
218+
219+
=== "kubeconfig"
220+
221+
```yaml
222+
apiVersion: v1
223+
clusters:
224+
- cluster:
225+
certificate-authority-data: <your-certificate-authority>
226+
# obtain the server url from the status of your WorkspaceType
227+
server: "<initializing-workspace-url>"
228+
name: finalizer
229+
contexts:
230+
- context:
231+
cluster: finalizer
232+
user: <user-with-sufficient-permissions>
233+
name: finalizer
234+
current-context: finalizer
235+
kind: Config
236+
preferences: {}
237+
users:
238+
- name: <user-with-sufficient-permissions>
239+
user:
240+
token: <user-token>
241+
```
242+
243+
=== "go.mod"
244+
245+
```Go
246+
...
247+
// replace upstream controller-runtime with kcp cluster aware fork
248+
replace sigs.k8s.io/controller-runtime v0.19.7 => github.com/kcp-dev/controller-runtime v0.19.0-kcp.1
249+
...
250+
```

0 commit comments

Comments
 (0)