-
Notifications
You must be signed in to change notification settings - Fork 8
Add initializing virtual workspace provider #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
0f422d5
wip: add initializing virtual workspace provider
cnvergence 4e306eb
refactor providers, moved to shared pkg and add e2e test
cnvergence af3d726
finish up the init vw e2e test
cnvergence dd68759
add workspace creation in example controller
cnvergence fdf1ed1
restore wildcard cache informer
cnvergence 8c0efbd
use mcpcache scoped cluster for initializing workspaces provider
cnvergence de20df4
update example controller with GET workspaces and create seperate sco…
cnvergence File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # `initializingworkspaces` Example Controller | ||
|
|
||
| This folder contains an example controller for the `initializingworkspaces` provider implementation. It reconciles `ConfigMap` objects across kcp workspaces. | ||
|
|
||
| It can be tested by applying the necessary manifests from the respective folder while connected to the `root` workspace of a kcp instance: | ||
|
|
||
| ```sh | ||
| $ kubectl apply -f ./manifests/bundle.yaml | ||
| workspacetype.tenancy.kcp.io/examples-initializingworkspaces-multicluster created | ||
| workspace.tenancy.kcp.io/example1 created | ||
| workspace.tenancy.kcp.io/example2 created | ||
| workspace.tenancy.kcp.io/example3 created | ||
| ``` | ||
|
|
||
| Then, start the example controller by passing the virtual workspace URL to it: | ||
|
|
||
| ```sh | ||
| $ go run . --server=$(kubectl get workspacetype examples-initializingworkspaces-multicluster -o jsonpath="{.status.virtualWorkspaces[0].url}") --initializer=root:examples-initializingworkspaces-multicluster | ||
| ``` | ||
|
|
||
| Observe the controller reconciling every logical cluster and creating the child workspace `initialized-workspace` and the `kcp-initializer-cm` ConfigMap in each workspace and removing the initializer when done. | ||
|
|
||
| ```sh | ||
| 2025-07-24T10:26:58+02:00 INFO Starting to initialize cluster {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "0c69fc44-4886-42f8-b620-82a6fe96a165", "cluster": "1i6ttu8js47cs302"} | ||
| 2025-07-24T10:26:58+02:00 INFO Creating child workspace {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "0c69fc44-4886-42f8-b620-82a6fe96a165", "cluster": "1i6ttu8js47cs302", "name": "initialized-workspace-1i6ttu8js47cs302"} | ||
| 2025-07-24T10:26:58+02:00 INFO kcp-initializing-workspaces-provider disengaging non-initializing workspace {"cluster": "1cxhyp0xy8lartoi"} | ||
| 2025-07-24T10:26:58+02:00 INFO Workspace created successfully {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "0c69fc44-4886-42f8-b620-82a6fe96a165", "cluster": "1i6ttu8js47cs302", "name": "initialized-workspace-1i6ttu8js47cs302"} | ||
| 2025-07-24T10:26:58+02:00 INFO Reconciling ConfigMap {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "0c69fc44-4886-42f8-b620-82a6fe96a165", "cluster": "1i6ttu8js47cs302", "name": "kcp-initializer-cm", "uuid": ""} | ||
| 2025-07-24T10:26:58+02:00 INFO ConfigMap created successfully {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "0c69fc44-4886-42f8-b620-82a6fe96a165", "cluster": "1i6ttu8js47cs302", "name": "kcp-initializer-cm", "uuid": "9a8a8d5d-d606-4e08-bb69-679719d94867"} | ||
| 2025-07-24T10:26:58+02:00 INFO Removed initializer from LogicalCluster status {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "0c69fc44-4886-42f8-b620-82a6fe96a165", "cluster": "1i6ttu8js47cs302", "name": "cluster", "uuid": "4c2fd3cf-512f-45f4-a9d3-6886c6542ccf"} | ||
| 2025-07-24T10:26:58+02:00 INFO Reconciling LogicalCluster {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "99bd39f0-3ea8-4805-9770-60f95127b5ac", "cluster": "2hwz9858cyir31hl", "logical cluster": {"owner":{"apiVersion":"tenancy.kcp.io/v1alpha1","resource":"workspaces","name":"example1","cluster":"root","uid":"1d79b26f-cfb8-40d5-934d-b4a61eb20f12"},"initializers":["root:universal","root:examples-initializingworkspaces-multicluster","system:apibindings"]}} | ||
| 2025-07-24T10:26:58+02:00 INFO Starting to initialize cluster {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "99bd39f0-3ea8-4805-9770-60f95127b5ac", "cluster": "2hwz9858cyir31hl"} | ||
| 2025-07-24T10:26:58+02:00 INFO Creating child workspace {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "99bd39f0-3ea8-4805-9770-60f95127b5ac", "cluster": "2hwz9858cyir31hl", "name": "initialized-workspace-2hwz9858cyir31hl"} | ||
| 2025-07-24T10:26:58+02:00 INFO kcp-initializing-workspaces-provider disengaging non-initializing workspace {"cluster": "1i6ttu8js47cs302"} | ||
| 2025-07-24T10:26:58+02:00 INFO Workspace created successfully {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "99bd39f0-3ea8-4805-9770-60f95127b5ac", "cluster": "2hwz9858cyir31hl", "name": "initialized-workspace-2hwz9858cyir31hl"} | ||
| 2025-07-24T10:26:58+02:00 INFO Reconciling ConfigMap {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "99bd39f0-3ea8-4805-9770-60f95127b5ac", "cluster": "2hwz9858cyir31hl", "name": "kcp-initializer-cm", "uuid": ""} | ||
| 2025-07-24T10:26:58+02:00 INFO ConfigMap created successfully {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "99bd39f0-3ea8-4805-9770-60f95127b5ac", "cluster": "2hwz9858cyir31hl", "name": "kcp-initializer-cm", "uuid": "87462d41-16b5-4617-9f7c-3894160576b7"} | ||
| 2025-07-24T10:26:58+02:00 INFO Removed initializer from LogicalCluster status {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "99bd39f0-3ea8-4805-9770-60f95127b5ac", "cluster": "2hwz9858cyir31hl", "name": "cluster", "uuid": "cfeee05f-3cba-4766-b464-ba3ebe41a3fa"} | ||
| 2025-07-24T10:26:58+02:00 INFO Reconciling LogicalCluster {"controller": "kcp-initializer-controller", "controllerGroup": "core.kcp.io", "controllerKind": "LogicalCluster", "reconcileID": "8ad43574-3862-4452-b1e0-e9daf1e67a54", "cluster": "2hwz9858cyir31hl", "logical cluster": {"owner":{"apiVersion":"tenancy.kcp.io/v1alpha1","resource":"workspaces","name":"example1","cluster":"root","uid":"1d79b26f-cfb8-40d5-934d-b4a61eb20f12"},"initializers":["root:universal","root:examples-initializingworkspaces-multicluster","system:apibindings"]}} | ||
| 2025-07-24T10:26:59+02:00 INFO kcp-initializing-workspaces-provider disengaging non-initializing workspace {"cluster": "2hwz9858cyir31hl"} | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| /* | ||
| Copyright 2025 The KCP Authors. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "slices" | ||
|
|
||
| "github.com/spf13/pflag" | ||
| "go.uber.org/zap/zapcore" | ||
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
| "k8s.io/apimachinery/pkg/util/runtime" | ||
| "k8s.io/client-go/kubernetes/scheme" | ||
| "k8s.io/client-go/rest" | ||
| ctrl "sigs.k8s.io/controller-runtime" | ||
| ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" | ||
| "sigs.k8s.io/controller-runtime/pkg/log" | ||
| "sigs.k8s.io/controller-runtime/pkg/log/zap" | ||
| "sigs.k8s.io/controller-runtime/pkg/manager" | ||
| "sigs.k8s.io/controller-runtime/pkg/manager/signals" | ||
| "sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
|
|
||
| mcbuilder "sigs.k8s.io/multicluster-runtime/pkg/builder" | ||
| mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" | ||
| mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile" | ||
|
|
||
| apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" | ||
| corev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" | ||
| "github.com/kcp-dev/kcp/sdk/apis/tenancy/initialization" | ||
| tenancyv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1" | ||
|
|
||
| "github.com/kcp-dev/multicluster-provider/initializingworkspaces" | ||
| ) | ||
|
|
||
| func init() { | ||
| runtime.Must(corev1alpha1.AddToScheme(scheme.Scheme)) | ||
| runtime.Must(tenancyv1alpha1.AddToScheme(scheme.Scheme)) | ||
| runtime.Must(apisv1alpha1.AddToScheme(scheme.Scheme)) | ||
| } | ||
|
|
||
| func main() { | ||
| var ( | ||
| server string | ||
| initializerName string | ||
| provider *initializingworkspaces.Provider | ||
| verbosity int | ||
| ) | ||
|
|
||
| pflag.StringVar(&server, "server", "", "Override for kubeconfig server URL") | ||
| pflag.StringVar(&initializerName, "initializer", "initializer:example", "Name of the initializer to use") | ||
| pflag.IntVar(&verbosity, "v", 1, "Log verbosity level") | ||
| pflag.Parse() | ||
|
|
||
| logOpts := zap.Options{ | ||
| Development: true, | ||
| Level: zapcore.Level(-verbosity), | ||
| } | ||
| log.SetLogger(zap.New(zap.UseFlagOptions(&logOpts))) | ||
|
|
||
| ctx := signals.SetupSignalHandler() | ||
| entryLog := log.Log.WithName("entrypoint") | ||
| cfg := ctrl.GetConfigOrDie() | ||
| cfg = rest.CopyConfig(cfg) | ||
|
|
||
| if server != "" { | ||
| cfg.Host = server | ||
| } | ||
|
|
||
| entryLog.Info("Setting up manager") | ||
| opts := manager.Options{} | ||
|
|
||
| var err error | ||
| provider, err = initializingworkspaces.New(cfg, initializingworkspaces.Options{InitializerName: initializerName}) | ||
| if err != nil { | ||
| entryLog.Error(err, "unable to construct cluster provider") | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| mgr, err := mcmanager.New(cfg, provider, opts) | ||
| if err != nil { | ||
| entryLog.Error(err, "unable to set up overall controller manager") | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| if err := mcbuilder.ControllerManagedBy(mgr). | ||
| Named("kcp-initializer-controller"). | ||
| For(&corev1alpha1.LogicalCluster{}). | ||
| Complete(mcreconcile.Func( | ||
| func(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { | ||
| log := log.FromContext(ctx).WithValues("cluster", req.ClusterName) | ||
| cl, err := mgr.GetCluster(ctx, req.ClusterName) | ||
| if err != nil { | ||
| return reconcile.Result{}, fmt.Errorf("failed to get cluster: %w", err) | ||
| } | ||
| client := cl.GetClient() | ||
| lc := &corev1alpha1.LogicalCluster{} | ||
| if err := client.Get(ctx, req.NamespacedName, lc); err != nil { | ||
| return reconcile.Result{}, fmt.Errorf("failed to get logical cluster: %w", err) | ||
| } | ||
|
|
||
| log.Info("Reconciling LogicalCluster", "logical cluster", lc.Spec) | ||
| initializer := corev1alpha1.LogicalClusterInitializer(initializerName) | ||
| // check if your initializer is still set on the logicalcluster | ||
| if slices.Contains(lc.Status.Initializers, initializer) { | ||
| log.Info("Starting to initialize cluster") | ||
| workspaceName := fmt.Sprintf("initialized-workspace-%s", req.ClusterName) | ||
| ws := &tenancyv1alpha1.Workspace{} | ||
| err = client.Get(ctx, ctrlclient.ObjectKey{Name: workspaceName}, ws) | ||
| if err != nil { | ||
| if !apierrors.IsNotFound(err) { | ||
| log.Error(err, "Error checking for existing workspace") | ||
| return reconcile.Result{}, nil | ||
| } | ||
|
|
||
| log.Info("Creating child workspace", "name", workspaceName) | ||
| ws = &tenancyv1alpha1.Workspace{ | ||
| ObjectMeta: ctrl.ObjectMeta{ | ||
| Name: workspaceName, | ||
| }, | ||
| } | ||
|
|
||
| if err := client.Create(ctx, ws); err != nil { | ||
| log.Error(err, "Failed to create workspace") | ||
| return reconcile.Result{}, nil | ||
| } | ||
| log.Info("Workspace created successfully", "name", workspaceName) | ||
| } | ||
|
|
||
| if ws.Status.Phase != corev1alpha1.LogicalClusterPhaseReady { | ||
| log.Info("Workspace not ready yet", "current-phase", ws.Status.Phase) | ||
| return reconcile.Result{Requeue: true}, nil | ||
| } | ||
| log.Info("Workspace is ready, proceeding to create ConfigMap") | ||
|
|
||
| s := &corev1.ConfigMap{ | ||
| ObjectMeta: ctrl.ObjectMeta{ | ||
| Name: "kcp-initializer-cm", | ||
| Namespace: "default", | ||
| }, | ||
| Data: map[string]string{ | ||
| "test-data": "example-value", | ||
| }, | ||
| } | ||
| log.Info("Reconciling ConfigMap", "name", s.Name, "uuid", s.UID) | ||
| if err := client.Create(ctx, s); err != nil { | ||
| return reconcile.Result{}, fmt.Errorf("failed to create configmap: %w", err) | ||
| } | ||
| log.Info("ConfigMap created successfully", "name", s.Name, "uuid", s.UID) | ||
| } | ||
| // Remove the initializer from the logical cluster status | ||
| // so that it won't be processed again. | ||
| if !slices.Contains(lc.Status.Initializers, initializer) { | ||
| log.Info("Initializer already absent, skipping patch") | ||
| return reconcile.Result{}, nil | ||
| } | ||
|
|
||
| patch := ctrlclient.MergeFrom(lc.DeepCopy()) | ||
| lc.Status.Initializers = initialization.EnsureInitializerAbsent(initializer, lc.Status.Initializers) | ||
| if err := client.Status().Patch(ctx, lc, patch); err != nil { | ||
| return reconcile.Result{}, err | ||
| } | ||
| log.Info("Removed initializer from LogicalCluster status", "name", lc.Name, "uuid", lc.UID) | ||
| return reconcile.Result{}, nil | ||
| }, | ||
| )); err != nil { | ||
| entryLog.Error(err, "failed to build controller") | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| if provider != nil { | ||
| entryLog.Info("Starting provider") | ||
| go func() { | ||
| if err := provider.Run(ctx, mgr); err != nil { | ||
| entryLog.Error(err, "unable to run provider") | ||
| os.Exit(1) | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| entryLog.Info("Starting manager") | ||
| if err := mgr.Start(ctx); err != nil { | ||
| entryLog.Error(err, "unable to run manager") | ||
| os.Exit(1) | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.