From cb6d3b89b8f89716f07cb0052b318176eeb16d6a Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 15 May 2026 09:46:59 +0200 Subject: [PATCH 01/26] Add ScopeableSharedIndexInformer.SetIgnoreFunc Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../apimachinery/pkg/cache/informers.go | 1 + .../third_party/informers/shared_informer.go | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go b/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go index 0f0a5b38edb..50d54950b72 100644 --- a/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go +++ b/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go @@ -29,5 +29,6 @@ import ( type ScopeableSharedIndexInformer interface { Cluster(clusterName logicalcluster.Name) cache.SharedIndexInformer ClusterWithContext(ctx context.Context, clusterName logicalcluster.Name) cache.SharedIndexInformer + SetIgnoreFunc(fn func(interface{}) bool) error cache.SharedIndexInformer } diff --git a/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go b/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go index 34dd1743d02..0236abda853 100644 --- a/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go +++ b/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "slices" "sync" "sync/atomic" "time" @@ -240,6 +241,10 @@ type sharedIndexInformer struct { // keyFunc is called when processing deltas by the underlying process function. keyFunc cache.KeyFunc + + // ignoreFunc is called for each object to be processed. If it + // returns true the object is skipped entirely. + ignoreFunc func(interface{}) bool } func (s *sharedIndexInformer) Cluster(cluster logicalcluster.Name) cache.SharedIndexInformer { @@ -325,6 +330,18 @@ func (s *sharedIndexInformer) SetTransform(handler cache.TransformFunc) error { return nil } +func (s *sharedIndexInformer) SetIgnoreFunc(fn func(interface{}) bool) error { + s.startedLock.Lock() + defer s.startedLock.Unlock() + + if s.started { + return fmt.Errorf("informer has already started") + } + + s.ignoreFunc = fn + return nil +} + func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) { s.RunWithContext(wait.ContextForChannel(stopCh)) } @@ -592,6 +609,23 @@ func (s *sharedIndexInformer) handleDeltas(logger klog.Logger, obj interface{}, defer s.blockDeltas.Unlock() if deltas, ok := obj.(cache.Deltas); ok { + if s.ignoreFunc != nil { + deltas = slices.DeleteFunc( + deltas, + func(d cache.Delta) bool { + return s.ignoreFunc(d.Object) + }, + ) + // ignoreFunc cannot act on ReplacedAllInfo, so filter the individual objects inside each ReplacedAll delta separately. + for i, d := range deltas { + if d.Type == cache.ReplacedAll { + if info, ok := d.Object.(cache.ReplacedAllInfo); ok { + info.Objects = slices.DeleteFunc(info.Objects, s.ignoreFunc) + deltas[i].Object = info + } + } + } + } return processDeltas(logger, s, s.indexer, deltas, isInInitialList, s.keyFunc) } return errors.New("object given as Process argument is not Deltas") @@ -600,6 +634,16 @@ func (s *sharedIndexInformer) handleDeltas(logger klog.Logger, obj interface{}, func (s *sharedIndexInformer) handleBatchDeltas(logger klog.Logger, deltas []cache.Delta, isInInitialList bool) error { s.blockDeltas.Lock() defer s.blockDeltas.Unlock() + + if s.ignoreFunc != nil { + deltas = slices.DeleteFunc( + deltas, + func(d cache.Delta) bool { + return s.ignoreFunc(d.Object) + }, + ) + } + return processDeltasInBatch(logger, s, s.indexer, deltas, isInInitialList, s.keyFunc) } From c6b889346c49bd38843f8caf4b11ecf6eaa30b66 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Tue, 2 Jun 2026 08:11:32 +0200 Subject: [PATCH 02/26] Handle ignoreFunc in DDSIF and add DDSIF.PurgeCluster Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/informer/informer.go | 40 +++++++++++++++++-- pkg/server/server.go | 4 +- .../cross_logical_cluster_list_test.go | 1 + 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/pkg/informer/informer.go b/pkg/informer/informer.go index f46cf9a89b3..33203a85efd 100644 --- a/pkg/informer/informer.go +++ b/pkg/informer/informer.go @@ -227,33 +227,45 @@ func NewScopedDiscoveringDynamicSharedInformerFactory( // shared informers that discovers new types and informs on updates to resources of // those types. // It receives the GVR-related information by delegating to a GVRSource. +// +// The filterFunc filters events going to handlers. +// The ignoreFunc is passed to .SetIgnoreFunc on each informer, +// filtering objects from being processed and stored at all. func NewDiscoveringDynamicSharedInformerFactory( dynamicClusterClient kcpdynamic.ClusterInterface, filterFunc func(obj interface{}) bool, + ignoreFunc func(obj interface{}) bool, tweakListOptions dynamicinformer.TweakListOptionsFunc, gvrSource GVRSource, indexers cache.Indexers, ) (*DiscoveringDynamicSharedInformerFactory, error) { + ddsif := &DiscoveringDynamicSharedInformerFactory{} + f, err := NewGenericDiscoveringDynamicSharedInformerFactory[kcpcache.ScopeableSharedIndexInformer, kcpcache.GenericClusterLister]( func(gvr schema.GroupVersionResource, resyncPeriod time.Duration, indexers cache.Indexers) kcpinformers.GenericClusterInformer { indexers[kcpcache.ClusterIndexName] = kcpcache.ClusterIndexFunc indexers[kcpcache.ClusterAndNamespaceIndexName] = kcpcache.ClusterAndNamespaceIndexFunc - return kcpdynamicinformer.NewFilteredDynamicInformer( + inf := kcpdynamicinformer.NewFilteredDynamicInformer( dynamicClusterClient, gvr, resyncPeriod, indexers, tweakListOptions, ) + if err := inf.Informer().SetIgnoreFunc(ignoreFunc); err != nil { + // This should never happen - .SetIgnoreFunc only errors when the informer + // is already running which shouldn't be the case here. + panic(fmt.Errorf("failed to set ignore func for %v: %w", gvr, err)) + } + return inf }, filterFunc, gvrSource, indexers, ) - return &DiscoveringDynamicSharedInformerFactory{ - GenericDiscoveringDynamicSharedInformerFactory: f, - }, err + ddsif.GenericDiscoveringDynamicSharedInformerFactory = f + return ddsif, err } // DiscoveringDynamicSharedInformerFactory is a factory for cluster-aware @@ -282,6 +294,26 @@ func (d *DiscoveringDynamicSharedInformerFactory) ClusterWithContext(ctx context return informer } +// PurgeCluster removes all objects belonging to the given logical cluster from all informer stores. +func (d *DiscoveringDynamicSharedInformerFactory) PurgeCluster(cluster logicalcluster.Name) { + d.informersLock.RLock() + defer d.informersLock.RUnlock() + + for _, inf := range d.informers { + store := inf.Informer().GetIndexer() + objs, err := store.ByIndex(kcpcache.ClusterIndexName, kcpcache.ClusterIndexKey(cluster)) + if err != nil { + utilruntime.HandleError(fmt.Errorf("failed to list objects for cluster %v: %w", cluster, err)) + continue + } + for _, obj := range objs { + if err := store.Delete(obj); err != nil { + utilruntime.HandleError(fmt.Errorf("failed to delete object from store for cluster %v: %w", cluster, err)) + } + } + } +} + type scopedDiscoveringDynamicSharedInformerFactory struct { *DiscoveringDynamicSharedInformerFactory cluster logicalcluster.Name diff --git a/pkg/server/server.go b/pkg/server/server.go index 30dcd0bae82..157501b9fdc 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -178,7 +178,8 @@ func NewServer(c CompletedConfig) (*Server, error) { s.PartialMetadataDDSIF, err = informer.NewDiscoveringDynamicSharedInformerFactory( metadataClusterClient, - func(obj interface{}) bool { return true }, + func(obj any) bool { return true }, + func(obj any) bool { return false }, nil, crdGVRSource, cache.Indexers{}, @@ -202,6 +203,7 @@ func NewServer(c CompletedConfig) (*Server, error) { s.CachePartialMetadataDDSIF, err = informer.NewDiscoveringDynamicSharedInformerFactory( cacheMetadataClusterClient, func(obj interface{}) bool { return true }, + func(obj any) bool { return false }, nil, cacheCrdGVRSource, cache.Indexers{}, diff --git a/test/e2e/conformance/cross_logical_cluster_list_test.go b/test/e2e/conformance/cross_logical_cluster_list_test.go index 3dd60cc390e..70e31a1339b 100644 --- a/test/e2e/conformance/cross_logical_cluster_list_test.go +++ b/test/e2e/conformance/cross_logical_cluster_list_test.go @@ -238,6 +238,7 @@ func TestCRDCrossLogicalClusterListPartialObjectMetadata(t *testing.T) { informerFactory, err := informer.NewDiscoveringDynamicSharedInformerFactory( metadataClusterClient, func(obj interface{}) bool { return true }, + func(obj interface{}) bool { return false }, nil, crdGVRSource, cache.Indexers{}, From 3e556425aa2d9a83e2fb2e670ac65721dcb6aace Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 15 May 2026 16:52:38 +0200 Subject: [PATCH 03/26] Add .ForceRelist Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/informer/informer.go | 11 +++++++ .../apimachinery/pkg/cache/informers.go | 1 + .../third_party/informers/shared_informer.go | 18 ++++++++++ .../third_party/reflector/controller.go | 11 +++++++ .../third_party/reflector/reflector.go | 33 +++++++++++++++++++ 5 files changed, 74 insertions(+) diff --git a/pkg/informer/informer.go b/pkg/informer/informer.go index 33203a85efd..9057ef4f77e 100644 --- a/pkg/informer/informer.go +++ b/pkg/informer/informer.go @@ -314,6 +314,17 @@ func (d *DiscoveringDynamicSharedInformerFactory) PurgeCluster(cluster logicalcl } } +// ForceRelist causes all informers to perform a full relist from the +// apiserver on the next list/watch cycle. +func (d *DiscoveringDynamicSharedInformerFactory) ForceRelist() { + d.informersLock.RLock() + defer d.informersLock.RUnlock() + + for _, inf := range d.informers { + inf.Informer().ForceRelist() + } +} + type scopedDiscoveringDynamicSharedInformerFactory struct { *DiscoveringDynamicSharedInformerFactory cluster logicalcluster.Name diff --git a/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go b/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go index 50d54950b72..bbfedbd92e1 100644 --- a/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go +++ b/staging/src/github.com/kcp-dev/apimachinery/pkg/cache/informers.go @@ -30,5 +30,6 @@ type ScopeableSharedIndexInformer interface { Cluster(clusterName logicalcluster.Name) cache.SharedIndexInformer ClusterWithContext(ctx context.Context, clusterName logicalcluster.Name) cache.SharedIndexInformer SetIgnoreFunc(fn func(interface{}) bool) error + ForceRelist() cache.SharedIndexInformer } diff --git a/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go b/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go index 0236abda853..16e774c6766 100644 --- a/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go +++ b/staging/src/github.com/kcp-dev/apimachinery/third_party/informers/shared_informer.go @@ -342,6 +342,24 @@ func (s *sharedIndexInformer) SetIgnoreFunc(fn func(interface{}) bool) error { return nil } +// ForceRelist causes the underlying reflector to perform a full relist +// on the next list/watch cycle. +func (s *sharedIndexInformer) ForceRelist() { + s.startedLock.Lock() + defer s.startedLock.Unlock() + + if !s.started { + return + } + + type forceRelister interface { + ForceRelist() + } + if r, ok := s.controller.(forceRelister); ok { + r.ForceRelist() + } +} + func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) { s.RunWithContext(wait.ContextForChannel(stopCh)) } diff --git a/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/controller.go b/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/controller.go index e725a9c2b6d..9e48ff2b5bf 100644 --- a/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/controller.go +++ b/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/controller.go @@ -193,6 +193,17 @@ func (c *controller) LastSyncResourceVersion() string { return c.reflector.LastSyncResourceVersion() } +// ForceRelist causes the underlying reflector to perform a full relist +// on the next list/watch cycle. +func (c *controller) ForceRelist() { + c.reflectorMutex.RLock() + defer c.reflectorMutex.RUnlock() + if c.reflector == nil { + return + } + c.reflector.ForceRelist() +} + // processLoop drains the work queue. // TODO: Consider doing the processing in parallel. This will require a little thought // to make sure that we don't end up processing the same object multiple times diff --git a/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/reflector.go b/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/reflector.go index ff0ca7a9250..5f6f137513e 100644 --- a/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/reflector.go +++ b/staging/src/github.com/kcp-dev/apimachinery/third_party/reflector/reflector.go @@ -27,6 +27,7 @@ import ( "reflect" "strings" "sync" + "sync/atomic" "time" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -174,6 +175,12 @@ type Reflector struct { isLastSyncResourceVersionUnavailable bool // lastSyncResourceVersionMutex guards read/write access to lastSyncResourceVersion lastSyncResourceVersionMutex sync.RWMutex + // activeWatch holds the current watch so ForceRelist can stop it. + activeWatch watch.Interface + activeWatchLock sync.Mutex + // relistRequested is set by ForceRelist to signal that watch() should + // return after the current watch ends, allowing a fresh list cycle. + relistRequested atomic.Bool // Called whenever the ListAndWatch drops the connection with an error. // kcp modification: use our local type instead of cache.WatchErrorHandlerWithContext watchErrorHandler WatchErrorHandlerWithContext @@ -541,6 +548,9 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha var err error retry := cache.NewRetryWithDeadline(r.MaxInternalErrorRetryDuration, time.Minute, apierrors.IsInternalError, r.clock) defer func() { + r.activeWatchLock.Lock() + r.activeWatch = nil + r.activeWatchLock.Unlock() if w != nil { w.Stop() } @@ -594,6 +604,10 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha } } + r.activeWatchLock.Lock() + r.activeWatch = w + r.activeWatchLock.Unlock() + err = handleWatch(ctx, start, w, r.store, r.expectedType, r.expectedGVK, r.name, r.typeDescription, func(rv string, eventReceivedBesidesAdded bool) { // We update the resource version in the store only if we have received at least one event that is @@ -614,9 +628,15 @@ func (r *Reflector) watch(ctx context.Context, w watch.Interface, resyncerrc cha } }, r.clock, resyncerrc) + r.activeWatchLock.Lock() + r.activeWatch = nil + r.activeWatchLock.Unlock() // handleWatch always stops the watcher. So we don't need to here. // Just set it to nil to trigger a retry on the next loop. w = nil + if r.relistRequested.CompareAndSwap(true, false) { + return nil + } retry.After(err) if err != nil { if !errors.Is(err, errorStopRequested) { @@ -1131,6 +1151,19 @@ func (r *Reflector) setIsLastSyncResourceVersionUnavailable(isUnavailable bool) r.isLastSyncResourceVersionUnavailable = isUnavailable } +// ForceRelist marks the last known resource version as unavailable, +// causing the next list/watch cycle to perform a full relist from etcd. +// It also stops the current watch so the relist happens immediately. +func (r *Reflector) ForceRelist() { + r.setIsLastSyncResourceVersionUnavailable(true) + r.relistRequested.Store(true) + r.activeWatchLock.Lock() + if r.activeWatch != nil { + r.activeWatch.Stop() + } + r.activeWatchLock.Unlock() +} + func isExpiredError(err error) bool { // In Kubernetes 1.17 and earlier, the api server returns both apierrors.StatusReasonExpired and // apierrors.StatusReasonGone for HTTP 410 (Gone) status code responses. In 1.18 the kube server is more consistent From 76cfd808c1f3d35d086ef909344487e7645b0568 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 15 May 2026 17:45:49 +0200 Subject: [PATCH 04/26] Add per cluster context cancellation integration test Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../per_cluster_context_test.go | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 test/integration/logicalclustermigration/per_cluster_context_test.go diff --git a/test/integration/logicalclustermigration/per_cluster_context_test.go b/test/integration/logicalclustermigration/per_cluster_context_test.go new file mode 100644 index 00000000000..564524d257c --- /dev/null +++ b/test/integration/logicalclustermigration/per_cluster_context_test.go @@ -0,0 +1,111 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + + "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/sdk/apis/core" + kcptesting "github.com/kcp-dev/sdk/testing" + + "github.com/kcp-dev/kcp/test/integration/framework" +) + +// TestPerClusterContextCancelsWatches verifies that active watches get +// cancelled when the context of the respective logical cluster is +// cancelled through the context manager. +func TestPerClusterContextCancelsWatches(t *testing.T) { + t.Parallel() + + server, kcpClientSet, kubeClient := framework.StartTestServer(t) + + workspace := kcptesting.NewLowLevelWorkspaceFixture(t, kcpClientSet, kcpClientSet, core.RootCluster.Path(), kcptesting.WithNamePrefix("ctx-cancel")) + lcName := logicalcluster.Name(workspace.Spec.Cluster) + + t.Logf("Opening watch on ConfigMaps in workspace %s", lcName) + watcher, err := kubeClient.Cluster(lcName.Path()).CoreV1().ConfigMaps("default").Watch(t.Context(), metav1.ListOptions{}) + require.NoError(t, err) + t.Cleanup(watcher.Stop) + + t.Logf("Verifying watch is alive by creating a ConfigMap and receiving an event") + _, err = kubeClient.Cluster(lcName.Path()).CoreV1().ConfigMaps("default").Create( + t.Context(), + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "trigger-cm"}, + }, + metav1.CreateOptions{}, + ) + require.NoError(t, err) + + select { + case event, ok := <-watcher.ResultChan(): + // TODO(ntnn): Verify the event matches the created configmap + require.True(t, ok, "watch channel closed unexpectedly before cancel") + require.NotEqual(t, watch.Error, event.Type, "unexpected error event before cancel") + case <-time.After(wait.ForeverTestTimeout): + t.Fatal("did not receive watch event - watch is not working") + } + + t.Logf("Cancelling per-cluster context for %s", lcName) + server.Server.ClusterContextManager.Delete(lcName.Path(), errors.New("cancelling logical cluster connections")) + + t.Logf("Verifying watch gets terminated") + select { + case _, ok := <-watcher.ResultChan(): + if ok { + // Might get an error event or the channel might deliver remaining buffered events. + // Drain until closed. + for range watcher.ResultChan() { + } + } + case <-time.After(wait.ForeverTestTimeout): + t.Fatal("watch was not terminated after context cancellation") + } + + t.Logf("Verifying a new watch still works after cancellation") + newWatcher, err := kubeClient.Cluster(lcName.Path()).CoreV1().ConfigMaps("default").Watch(t.Context(), metav1.ListOptions{}) + require.NoError(t, err) + t.Cleanup(newWatcher.Stop) + + _, err = kubeClient.Cluster(lcName.Path()).CoreV1().ConfigMaps("default").Create( + t.Context(), + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "post-cancel-cm"}, + }, + metav1.CreateOptions{}, + ) + require.NoError(t, err) + + select { + case event, ok := <-newWatcher.ResultChan(): + // TODO(ntnn): Verify the event matches the created configmap + require.True(t, ok, "new watch channel closed unexpectedly") + require.NotEqual(t, watch.Error, event.Type, "unexpected error event on new watch") + case <-time.After(wait.ForeverTestTimeout): + t.Fatal("new watch did not receive event - new watches are broken after cancel") + } +} From c470fcea14c892b6df5c6e5e87b6eeaa51596d5c Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Thu, 4 Jun 2026 09:29:40 +0200 Subject: [PATCH 05/26] Add migration.kcp.io Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../sdk/apis/migrating/install/install.go | 31 ++++ .../kcp-dev/sdk/apis/migrating/register.go | 21 +++ .../sdk/apis/migrating/v1alpha1/doc.go | 21 +++ .../sdk/apis/migrating/v1alpha1/register.go | 53 +++++++ .../v1alpha1/types_logicalclusterdump.go | 75 ++++++++++ .../v1alpha1/types_logicalclustermigration.go | 141 ++++++++++++++++++ 6 files changed, 342 insertions(+) create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migrating/install/install.go create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migrating/register.go create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/doc.go create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/register.go create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclusterdump.go create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclustermigration.go diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/install/install.go b/staging/src/github.com/kcp-dev/sdk/apis/migrating/install/install.go new file mode 100644 index 00000000000..d41bca0f73e --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migrating/install/install.go @@ -0,0 +1,31 @@ +/* +Copyright 2026 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 install installs the v1alpha1 monolithic api, making it available as an +// option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/kcp-dev/sdk/apis/migrating/v1alpha1" +) + +// Install registers the API group and adds types to a scheme. +func Install(scheme *runtime.Scheme) { + utilruntime.Must(v1alpha1.AddToScheme(scheme)) +} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/register.go b/staging/src/github.com/kcp-dev/sdk/apis/migrating/register.go new file mode 100644 index 00000000000..9cd61d59025 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migrating/register.go @@ -0,0 +1,21 @@ +/* +Copyright 2026 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 migrating + +const ( + GroupName = "migration.kcp.io" +) diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/doc.go b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/doc.go new file mode 100644 index 00000000000..a4d7d23723f --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2026 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. +*/ + +// +k8s:deepcopy-gen=package,register +// +groupName=migration.kcp.io +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=com.github.kcp-dev.sdk.apis.migrating.v1alpha1 +package v1alpha1 diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/register.go b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/register.go new file mode 100644 index 00000000000..0edc10fb858 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/register.go @@ -0,0 +1,53 @@ +/* +Copyright 2026 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/kcp-dev/sdk/apis/migrating" +) + +// SchemeGroupVersion is group version used to register these objects. +var SchemeGroupVersion = schema.GroupVersion{Group: migrating.GroupName, Version: "v1alpha1"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind. +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource. +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &LogicalClusterDump{}, + &LogicalClusterMigration{}, + &LogicalClusterMigrationList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclusterdump.go b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclusterdump.go new file mode 100644 index 00000000000..5b71ff9ad25 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclusterdump.go @@ -0,0 +1,75 @@ +/* +Copyright 2026 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// LogicalClusterDump is an ephemeral request/response type used by the +// destination shard during a logical cluster migration to fetch the raw +// etcd contents of the migrating logical cluster from the origin shard. +// +// The server populates Status.Entries on Create. The object is not +// persisted. +// +// +genclient +// +genclient:nonNamespaced +// +genclient:onlyVerbs=create +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type LogicalClusterDump struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +optional + Spec LogicalClusterDumpSpec `json:"spec,omitempty"` + // +optional + Status LogicalClusterDumpStatus `json:"status,omitempty"` +} + +// LogicalClusterDumpSpec is the desired state for a dump request. +// +// The logical cluster to dump is taken from the request's cluster context +// (i.e. the URL the request arrived on). No fields are required today. +// +// TODO: add Continue string for pagination. +type LogicalClusterDumpSpec struct{} + +// LogicalClusterDumpStatus carries the dump payload populated by the server. +type LogicalClusterDumpStatus struct { + // entries is the full set of etcd key/value pairs belonging to the + // logical cluster, in the origin shard's etcd encoding (proto for + // built-ins, JSON for CRs). + // + // TODO: support pagination via a Continue token on Spec/Status. + // TODO: switch to NDJSON streaming once entry count or total size + // becomes a concern. + // + // +optional + Entries []EtcdEntry `json:"entries,omitempty"` +} + +// EtcdEntry is a single etcd key/value pair from the origin shard. +type EtcdEntry struct { + // key is the etcd key with the origin shard's storage prefix stripped. + // The destination shard prepends its own storage prefix before writing. + Key string `json:"key"` + + // value is the raw etcd value bytes. JSON-encoded as base64 on the wire. + Value []byte `json:"value"` +} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclustermigration.go new file mode 100644 index 00000000000..004b3956762 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclustermigration.go @@ -0,0 +1,141 @@ +/* +Copyright 2026 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 v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" + "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" +) + +// LogicalClusterMigration describes a migration of a logical cluster from one shard to another. +// +// +crd +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories=kcp +// +kubebuilder:printcolumn:name="Logical Cluster",type=string,JSONPath=`.spec.logicalCluster`,description="The logical cluster being migrated" +// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`,description="The current phase of the migration" +// +kubebuilder:printcolumn:name="Destination",type=string,JSONPath=`.spec.destinationShard`,description="The destination shard" +type LogicalClusterMigration struct { + v1.TypeMeta `json:",inline"` + // +optional + v1.ObjectMeta `json:"metadata,omitempty"` + // +optional + Spec LogicalClusterMigrationSpec `json:"spec,omitempty"` + // +optional + Status LogicalClusterMigrationStatus `json:"status,omitempty"` +} + +// LogicalClusterMigrationSpec holds the desired state of the migration. +type LogicalClusterMigrationSpec struct { + // logicalCluster is the name of the logical cluster to migrate. + // + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + LogicalCluster string `json:"logicalCluster"` + + // destinationShard is the name of the shard to migrate the logical cluster to. + // + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + DestinationShard string `json:"destinationShard"` +} + +// LogicalClusterMigrationPhaseType is the type of the current phase of the migration. +// +// +kubebuilder:validation:Enum=Preparing;Migrating;OriginCleanup;DestinationFinalize;Completed;Failed +type LogicalClusterMigrationPhaseType string + +const ( + LogicalClusterMigrationPhasePreparing LogicalClusterMigrationPhaseType = "Preparing" + LogicalClusterMigrationPhaseMigrating LogicalClusterMigrationPhaseType = "Migrating" + LogicalClusterMigrationPhaseOriginCleanup LogicalClusterMigrationPhaseType = "OriginCleanup" + LogicalClusterMigrationPhaseDestinationFinalize LogicalClusterMigrationPhaseType = "DestinationFinalize" + LogicalClusterMigrationPhaseCompleted LogicalClusterMigrationPhaseType = "Completed" + LogicalClusterMigrationPhaseFailed LogicalClusterMigrationPhaseType = "Failed" +) + +// LogicalClusterMigrationStatus communicates the observed state of the migration. +type LogicalClusterMigrationStatus struct { + // phase is the current phase of the migration. + // + // +optional + // +kubebuilder:default=Preparing + Phase LogicalClusterMigrationPhaseType `json:"phase,omitempty"` + + // originShard is the name of the shard to migrate the logical cluster from. + // Set by the origin shard controller during preparation. + // + // +optional + OriginShard string `json:"originShard,omitempty"` + + // Current processing state of the migration. + // +optional + Conditions conditionsv1alpha1.Conditions `json:"conditions,omitempty"` +} + +const ( + // LCMigrationOriginPreparing indicates the origin shard is preparing for migration. + LCMigrationOriginPreparing conditionsv1alpha1.ConditionType = "OriginPreparing" + + // LCMigrationOriginReady indicates the origin shard has completed preparation + // and the destination shard can start copying data. + LCMigrationOriginReady conditionsv1alpha1.ConditionType = "OriginReady" + + // LCMigrationStarted indicates the destination shard has started copying data + // from the origin. + LCMigrationStarted conditionsv1alpha1.ConditionType = "MigrationStarted" + + // LCMigrationDataCopied indicates the destination shard has finished copying + // and verifying all data. + LCMigrationDataCopied conditionsv1alpha1.ConditionType = "DataCopied" + + // LCMigrationOriginCleaned indicates the origin shard has deleted all objects + // belonging to the migrating logical cluster. + LCMigrationOriginCleaned conditionsv1alpha1.ConditionType = "OriginCleaned" + + // LCMigrationCompleted indicates the migration has fully completed and the + // logical cluster is available on the destination shard. + LCMigrationCompleted conditionsv1alpha1.ConditionType = "Completed" +) + +func (in *LogicalClusterMigration) SetConditions(c conditionsv1alpha1.Conditions) { + in.Status.Conditions = c +} + +func (in *LogicalClusterMigration) GetConditions() conditionsv1alpha1.Conditions { + return in.Status.Conditions +} + +var _ conditions.Getter = &LogicalClusterMigration{} +var _ conditions.Setter = &LogicalClusterMigration{} + +// LogicalClusterMigrationList is a list of LogicalClusterMigration resources. +// +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type LogicalClusterMigrationList struct { + v1.TypeMeta `json:",inline"` + v1.ListMeta `json:"metadata"` + + Items []LogicalClusterMigration `json:"items"` +} From 75b25683444a86a04e0bad6fb5d27d418fc5a8a8 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Thu, 4 Jun 2026 09:39:04 +0200 Subject: [PATCH 06/26] Add migration.kcp.io to codegen Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- hack/update-codegen-clients.sh | 4 ++++ .../sdk/apis/{migrating => migration}/install/install.go | 2 +- .../kcp-dev/sdk/apis/{migrating => migration}/register.go | 2 +- .../kcp-dev/sdk/apis/{migrating => migration}/v1alpha1/doc.go | 2 +- .../sdk/apis/{migrating => migration}/v1alpha1/register.go | 4 ++-- .../v1alpha1/types_logicalclusterdump.go | 2 +- .../v1alpha1/types_logicalclustermigration.go | 2 +- 7 files changed, 11 insertions(+), 7 deletions(-) rename staging/src/github.com/kcp-dev/sdk/apis/{migrating => migration}/install/install.go (95%) rename staging/src/github.com/kcp-dev/sdk/apis/{migrating => migration}/register.go (97%) rename staging/src/github.com/kcp-dev/sdk/apis/{migrating => migration}/v1alpha1/doc.go (98%) rename staging/src/github.com/kcp-dev/sdk/apis/{migrating => migration}/v1alpha1/register.go (93%) rename staging/src/github.com/kcp-dev/sdk/apis/{migrating => migration}/v1alpha1/types_logicalclusterdump.go (97%) rename staging/src/github.com/kcp-dev/sdk/apis/{migrating => migration}/v1alpha1/types_logicalclustermigration.go (99%) diff --git a/hack/update-codegen-clients.sh b/hack/update-codegen-clients.sh index a8bff80dfec..1b46bca3310 100755 --- a/hack/update-codegen-clients.sh +++ b/hack/update-codegen-clients.sh @@ -51,6 +51,7 @@ mkdir -p ${SDK_PKG}/client/{clientset,applyconfiguration,listers,informers} github.com/kcp-dev/sdk/apis/apis/v1alpha1 \ github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1 \ k8s.io/apimachinery/pkg/apis/meta/v1 \ @@ -67,6 +68,7 @@ mkdir -p ${SDK_PKG}/client/{clientset,applyconfiguration,listers,informers} --input github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ --input github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ --input github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + --input github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ --input-base="" \ --apply-configuration-package=github.com/kcp-dev/sdk/client/applyconfiguration \ --clientset-name "versioned" @@ -149,6 +151,7 @@ go install "${OPENAPI_PKG}"/cmd/openapi-gen github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1 "$GOPATH"/bin/openapi-gen \ @@ -162,6 +165,7 @@ go install "${OPENAPI_PKG}"/cmd/openapi-gen github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1 \ k8s.io/apimachinery/pkg/apis/meta/v1 \ k8s.io/apimachinery/pkg/runtime \ diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/install/install.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/install/install.go similarity index 95% rename from staging/src/github.com/kcp-dev/sdk/apis/migrating/install/install.go rename to staging/src/github.com/kcp-dev/sdk/apis/migration/install/install.go index d41bca0f73e..8ce2da08746 100644 --- a/staging/src/github.com/kcp-dev/sdk/apis/migrating/install/install.go +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/install/install.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "github.com/kcp-dev/sdk/apis/migrating/v1alpha1" + "github.com/kcp-dev/sdk/apis/migration/v1alpha1" ) // Install registers the API group and adds types to a scheme. diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/register.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/register.go similarity index 97% rename from staging/src/github.com/kcp-dev/sdk/apis/migrating/register.go rename to staging/src/github.com/kcp-dev/sdk/apis/migration/register.go index 9cd61d59025..87932a471cf 100644 --- a/staging/src/github.com/kcp-dev/sdk/apis/migrating/register.go +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/register.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package migrating +package migration const ( GroupName = "migration.kcp.io" diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/doc.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/doc.go similarity index 98% rename from staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/doc.go rename to staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/doc.go index a4d7d23723f..2089ae73693 100644 --- a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/doc.go +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/doc.go @@ -17,5 +17,5 @@ limitations under the License. // +k8s:deepcopy-gen=package,register // +groupName=migration.kcp.io // +k8s:openapi-gen=true -// +k8s:openapi-model-package=com.github.kcp-dev.sdk.apis.migrating.v1alpha1 +// +k8s:openapi-model-package=com.github.kcp-dev.sdk.apis.migration.v1alpha1 package v1alpha1 diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/register.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/register.go similarity index 93% rename from staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/register.go rename to staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/register.go index 0edc10fb858..ef28b53d202 100644 --- a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/register.go +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/register.go @@ -21,11 +21,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/kcp-dev/sdk/apis/migrating" + "github.com/kcp-dev/sdk/apis/migration" ) // SchemeGroupVersion is group version used to register these objects. -var SchemeGroupVersion = schema.GroupVersion{Group: migrating.GroupName, Version: "v1alpha1"} +var SchemeGroupVersion = schema.GroupVersion{Group: migration.GroupName, Version: "v1alpha1"} // Kind takes an unqualified kind and returns back a Group qualified GroupKind. func Kind(kind string) schema.GroupKind { diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclusterdump.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/types_logicalclusterdump.go similarity index 97% rename from staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclusterdump.go rename to staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/types_logicalclusterdump.go index 5b71ff9ad25..5eeb7dff8d5 100644 --- a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclusterdump.go +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/types_logicalclusterdump.go @@ -22,7 +22,7 @@ import ( // LogicalClusterDump is an ephemeral request/response type used by the // destination shard during a logical cluster migration to fetch the raw -// etcd contents of the migrating logical cluster from the origin shard. +// etcd contents of the migration logical cluster from the origin shard. // // The server populates Status.Entries on Create. The object is not // persisted. diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/types_logicalclustermigration.go similarity index 99% rename from staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclustermigration.go rename to staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/types_logicalclustermigration.go index 004b3956762..1a0331170dd 100644 --- a/staging/src/github.com/kcp-dev/sdk/apis/migrating/v1alpha1/types_logicalclustermigration.go +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/types_logicalclustermigration.go @@ -111,7 +111,7 @@ const ( LCMigrationDataCopied conditionsv1alpha1.ConditionType = "DataCopied" // LCMigrationOriginCleaned indicates the origin shard has deleted all objects - // belonging to the migrating logical cluster. + // belonging to the migration logical cluster. LCMigrationOriginCleaned conditionsv1alpha1.ConditionType = "OriginCleaned" // LCMigrationCompleted indicates the migration has fully completed and the From ca23c5193b50b78ca049226deb3ab7a46d8766c1 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Thu, 4 Jun 2026 10:01:43 +0200 Subject: [PATCH 07/26] codegen Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../migration.kcp.io_logicalclusterdumps.yaml | 85 +++++ ...ation.kcp.io_logicalclustermigrations.yaml | 143 +++++++++ .../apiexport-migration.kcp.io.yaml | 17 + ...-logicalclusterdumps.migration.kcp.io.yaml | 82 +++++ ...calclustermigrations.migration.kcp.io.yaml | 138 ++++++++ .../v1alpha1/zz_generated.deepcopy.go | 216 +++++++++++++ .../v1alpha1/zz_generated.model_name.go | 62 ++++ .../v1alpha1/logicalclustermigration.go | 245 +++++++++++++++ .../v1alpha1/logicalclustermigrationspec.go | 52 ++++ .../v1alpha1/logicalclustermigrationstatus.go | 68 ++++ .../sdk/client/applyconfiguration/utils.go | 10 + .../client/clientset/versioned/clientset.go | 25 +- .../clientset/versioned/cluster/clientset.go | 27 +- .../versioned/cluster/fake/clientset.go | 13 + .../versioned/cluster/fake/register.go | 2 + .../versioned/cluster/scheme/register.go | 2 + .../cluster/typed/migration/v1alpha1/doc.go | 20 ++ .../typed/migration/v1alpha1/fake/doc.go | 20 ++ .../v1alpha1/fake/logicalclusterdump.go | 72 +++++ .../v1alpha1/fake/logicalclustermigration.go | 93 ++++++ .../v1alpha1/fake/migration_client.go | 69 ++++ .../migration/v1alpha1/generated_expansion.go | 23 ++ .../migration/v1alpha1/logicalclusterdump.go | 52 ++++ .../v1alpha1/logicalclustermigration.go | 69 ++++ .../migration/v1alpha1/migration_client.go | 108 +++++++ .../versioned/fake/clientset_generated.go | 7 + .../clientset/versioned/fake/register.go | 2 + .../clientset/versioned/scheme/register.go | 2 + .../versioned/typed/migration/v1alpha1/doc.go | 20 ++ .../typed/migration/v1alpha1/fake/doc.go | 20 ++ .../v1alpha1/fake/fake_logicalclusterdump.go | 45 +++ .../fake/fake_logicalclustermigration.go | 54 ++++ .../v1alpha1/fake/fake_migration_client.go | 45 +++ .../migration/v1alpha1/generated_expansion.go | 23 ++ .../migration/v1alpha1/logicalclusterdump.go | 59 ++++ .../v1alpha1/logicalclustermigration.go | 77 +++++ .../migration/v1alpha1/migration_client.go | 107 +++++++ .../informers/externalversions/factory.go | 11 + .../informers/externalversions/generic.go | 10 + .../externalversions/migration/interface.go | 67 ++++ .../migration/v1alpha1/interface.go | 64 ++++ .../v1alpha1/logicalclustermigration.go | 182 +++++++++++ .../migration/v1alpha1/expansion_generated.go | 27 ++ .../v1alpha1/logicalclustermigration.go | 102 ++++++ .../sdk/openapi/zz_generated.openapi.go | 294 ++++++++++++++++++ 45 files changed, 2918 insertions(+), 13 deletions(-) create mode 100644 config/crds/migration.kcp.io_logicalclusterdumps.yaml create mode 100644 config/crds/migration.kcp.io_logicalclustermigrations.yaml create mode 100644 config/root-phase0/apiexport-migration.kcp.io.yaml create mode 100644 config/root-phase0/apiresourceschema-logicalclusterdumps.migration.kcp.io.yaml create mode 100644 config/root-phase0/apiresourceschema-logicalclustermigrations.migration.kcp.io.yaml create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.deepcopy.go create mode 100644 staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.model_name.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigration.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationspec.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationstatus.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/doc.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/doc.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclusterdump.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclustermigration.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/migration_client.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/generated_expansion.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclusterdump.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclustermigration.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/migration_client.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/doc.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/doc.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclusterdump.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclustermigration.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_migration_client.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/generated_expansion.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclusterdump.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclustermigration.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/migration_client.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/interface.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/interface.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/logicalclustermigration.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/expansion_generated.go create mode 100644 staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/logicalclustermigration.go diff --git a/config/crds/migration.kcp.io_logicalclusterdumps.yaml b/config/crds/migration.kcp.io_logicalclusterdumps.yaml new file mode 100644 index 00000000000..d5059e5c98c --- /dev/null +++ b/config/crds/migration.kcp.io_logicalclusterdumps.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: logicalclusterdumps.migration.kcp.io +spec: + group: migration.kcp.io + names: + kind: LogicalClusterDump + listKind: LogicalClusterDumpList + plural: logicalclusterdumps + singular: logicalclusterdump + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + LogicalClusterDump is an ephemeral request/response type used by the + destination shard during a logical cluster migration to fetch the raw + etcd contents of the migration logical cluster from the origin shard. + + The server populates Status.Entries on Create. The object is not + persisted. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: | + LogicalClusterDumpSpec is the desired state for a dump request. + + The logical cluster to dump is taken from the request's cluster context + (i.e. the URL the request arrived on). No fields are required today. + type: object + status: + description: LogicalClusterDumpStatus carries the dump payload populated + by the server. + properties: + entries: + description: |- + entries is the full set of etcd key/value pairs belonging to the + logical cluster, in the origin shard's etcd encoding (proto for + built-ins, JSON for CRs). + + becomes a concern. + items: + description: EtcdEntry is a single etcd key/value pair from the + origin shard. + properties: + key: + description: |- + key is the etcd key with the origin shard's storage prefix stripped. + The destination shard prepends its own storage prefix before writing. + type: string + value: + description: value is the raw etcd value bytes. JSON-encoded + as base64 on the wire. + format: byte + type: string + required: + - key + - value + type: object + type: array + type: object + type: object + served: true + storage: true diff --git a/config/crds/migration.kcp.io_logicalclustermigrations.yaml b/config/crds/migration.kcp.io_logicalclustermigrations.yaml new file mode 100644 index 00000000000..c8cb4536e92 --- /dev/null +++ b/config/crds/migration.kcp.io_logicalclustermigrations.yaml @@ -0,0 +1,143 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: logicalclustermigrations.migration.kcp.io +spec: + group: migration.kcp.io + names: + categories: + - kcp + kind: LogicalClusterMigration + listKind: LogicalClusterMigrationList + plural: logicalclustermigrations + singular: logicalclustermigration + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The logical cluster being migrated + jsonPath: .spec.logicalCluster + name: Logical Cluster + type: string + - description: The current phase of the migration + jsonPath: .status.phase + name: Phase + type: string + - description: The destination shard + jsonPath: .spec.destinationShard + name: Destination + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: LogicalClusterMigration describes a migration of a logical cluster + from one shard to another. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: LogicalClusterMigrationSpec holds the desired state of the + migration. + properties: + destinationShard: + description: destinationShard is the name of the shard to migrate + the logical cluster to. + minLength: 1 + type: string + logicalCluster: + description: logicalCluster is the name of the logical cluster to + migrate. + minLength: 1 + type: string + required: + - destinationShard + - logicalCluster + type: object + status: + description: LogicalClusterMigrationStatus communicates the observed state + of the migration. + properties: + conditions: + description: Current processing state of the migration. + items: + description: Condition defines an observation of a object operational + state. + properties: + lastTransitionTime: + description: |- + Last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + A human readable message indicating details about the transition. + This field may be empty. + type: string + reason: + description: |- + The reason for the condition's last transition in CamelCase. + The specific API may choose whether or not this field is considered a guaranteed API. + This field may not be empty. + type: string + severity: + description: |- + Severity provides an explicit classification of Reason code, so the users or machines can immediately + understand the current situation and act accordingly. + The Severity field MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: |- + Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability to deconflict is important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + originShard: + description: |- + originShard is the name of the shard to migrate the logical cluster from. + Set by the origin shard controller during preparation. + type: string + phase: + default: Preparing + description: phase is the current phase of the migration. + enum: + - Preparing + - Migrating + - OriginCleanup + - DestinationFinalize + - Completed + - Failed + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/root-phase0/apiexport-migration.kcp.io.yaml b/config/root-phase0/apiexport-migration.kcp.io.yaml new file mode 100644 index 00000000000..ed510d59a93 --- /dev/null +++ b/config/root-phase0/apiexport-migration.kcp.io.yaml @@ -0,0 +1,17 @@ +apiVersion: apis.kcp.io/v1alpha2 +kind: APIExport +metadata: + name: migration.kcp.io +spec: + resources: + - group: migration.kcp.io + name: logicalclusterdumps + schema: v260604-70219e163.logicalclusterdumps.migration.kcp.io + storage: + crd: {} + - group: migration.kcp.io + name: logicalclustermigrations + schema: v260604-70219e163.logicalclustermigrations.migration.kcp.io + storage: + crd: {} +status: {} diff --git a/config/root-phase0/apiresourceschema-logicalclusterdumps.migration.kcp.io.yaml b/config/root-phase0/apiresourceschema-logicalclusterdumps.migration.kcp.io.yaml new file mode 100644 index 00000000000..f789767cf92 --- /dev/null +++ b/config/root-phase0/apiresourceschema-logicalclusterdumps.migration.kcp.io.yaml @@ -0,0 +1,82 @@ +apiVersion: apis.kcp.io/v1alpha1 +kind: APIResourceSchema +metadata: + name: v260604-70219e163.logicalclusterdumps.migration.kcp.io +spec: + group: migration.kcp.io + names: + kind: LogicalClusterDump + listKind: LogicalClusterDumpList + plural: logicalclusterdumps + singular: logicalclusterdump + scope: Namespaced + versions: + - name: v1alpha1 + schema: + description: |- + LogicalClusterDump is an ephemeral request/response type used by the + destination shard during a logical cluster migration to fetch the raw + etcd contents of the migration logical cluster from the origin shard. + + The server populates Status.Entries on Create. The object is not + persisted. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: | + LogicalClusterDumpSpec is the desired state for a dump request. + + The logical cluster to dump is taken from the request's cluster context + (i.e. the URL the request arrived on). No fields are required today. + type: object + status: + description: LogicalClusterDumpStatus carries the dump payload populated + by the server. + properties: + entries: + description: |- + entries is the full set of etcd key/value pairs belonging to the + logical cluster, in the origin shard's etcd encoding (proto for + built-ins, JSON for CRs). + + becomes a concern. + items: + description: EtcdEntry is a single etcd key/value pair from the origin + shard. + properties: + key: + description: |- + key is the etcd key with the origin shard's storage prefix stripped. + The destination shard prepends its own storage prefix before writing. + type: string + value: + description: value is the raw etcd value bytes. JSON-encoded as + base64 on the wire. + format: byte + type: string + required: + - key + - value + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: {} diff --git a/config/root-phase0/apiresourceschema-logicalclustermigrations.migration.kcp.io.yaml b/config/root-phase0/apiresourceschema-logicalclustermigrations.migration.kcp.io.yaml new file mode 100644 index 00000000000..f1811e467aa --- /dev/null +++ b/config/root-phase0/apiresourceschema-logicalclustermigrations.migration.kcp.io.yaml @@ -0,0 +1,138 @@ +apiVersion: apis.kcp.io/v1alpha1 +kind: APIResourceSchema +metadata: + name: v260604-70219e163.logicalclustermigrations.migration.kcp.io +spec: + group: migration.kcp.io + names: + categories: + - kcp + kind: LogicalClusterMigration + listKind: LogicalClusterMigrationList + plural: logicalclustermigrations + singular: logicalclustermigration + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The logical cluster being migrated + jsonPath: .spec.logicalCluster + name: Logical Cluster + type: string + - description: The current phase of the migration + jsonPath: .status.phase + name: Phase + type: string + - description: The destination shard + jsonPath: .spec.destinationShard + name: Destination + type: string + name: v1alpha1 + schema: + description: LogicalClusterMigration describes a migration of a logical cluster + from one shard to another. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: LogicalClusterMigrationSpec holds the desired state of the + migration. + properties: + destinationShard: + description: destinationShard is the name of the shard to migrate the + logical cluster to. + minLength: 1 + type: string + logicalCluster: + description: logicalCluster is the name of the logical cluster to migrate. + minLength: 1 + type: string + required: + - destinationShard + - logicalCluster + type: object + status: + description: LogicalClusterMigrationStatus communicates the observed state + of the migration. + properties: + conditions: + description: Current processing state of the migration. + items: + description: Condition defines an observation of a object operational + state. + properties: + lastTransitionTime: + description: |- + Last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + A human readable message indicating details about the transition. + This field may be empty. + type: string + reason: + description: |- + The reason for the condition's last transition in CamelCase. + The specific API may choose whether or not this field is considered a guaranteed API. + This field may not be empty. + type: string + severity: + description: |- + Severity provides an explicit classification of Reason code, so the users or machines can immediately + understand the current situation and act accordingly. + The Severity field MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: |- + Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability to deconflict is important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + originShard: + description: |- + originShard is the name of the shard to migrate the logical cluster from. + Set by the origin shard controller during preparation. + type: string + phase: + default: Preparing + description: phase is the current phase of the migration. + enum: + - Preparing + - Migrating + - OriginCleanup + - DestinationFinalize + - Completed + - Failed + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.deepcopy.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..7c61a814e3a --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,216 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EtcdEntry) DeepCopyInto(out *EtcdEntry) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make([]byte, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EtcdEntry. +func (in *EtcdEntry) DeepCopy() *EtcdEntry { + if in == nil { + return nil + } + out := new(EtcdEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogicalClusterDump) DeepCopyInto(out *LogicalClusterDump) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalClusterDump. +func (in *LogicalClusterDump) DeepCopy() *LogicalClusterDump { + if in == nil { + return nil + } + out := new(LogicalClusterDump) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LogicalClusterDump) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogicalClusterDumpSpec) DeepCopyInto(out *LogicalClusterDumpSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalClusterDumpSpec. +func (in *LogicalClusterDumpSpec) DeepCopy() *LogicalClusterDumpSpec { + if in == nil { + return nil + } + out := new(LogicalClusterDumpSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogicalClusterDumpStatus) DeepCopyInto(out *LogicalClusterDumpStatus) { + *out = *in + if in.Entries != nil { + in, out := &in.Entries, &out.Entries + *out = make([]EtcdEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalClusterDumpStatus. +func (in *LogicalClusterDumpStatus) DeepCopy() *LogicalClusterDumpStatus { + if in == nil { + return nil + } + out := new(LogicalClusterDumpStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogicalClusterMigration) DeepCopyInto(out *LogicalClusterMigration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalClusterMigration. +func (in *LogicalClusterMigration) DeepCopy() *LogicalClusterMigration { + if in == nil { + return nil + } + out := new(LogicalClusterMigration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LogicalClusterMigration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogicalClusterMigrationList) DeepCopyInto(out *LogicalClusterMigrationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LogicalClusterMigration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalClusterMigrationList. +func (in *LogicalClusterMigrationList) DeepCopy() *LogicalClusterMigrationList { + if in == nil { + return nil + } + out := new(LogicalClusterMigrationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LogicalClusterMigrationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogicalClusterMigrationSpec) DeepCopyInto(out *LogicalClusterMigrationSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalClusterMigrationSpec. +func (in *LogicalClusterMigrationSpec) DeepCopy() *LogicalClusterMigrationSpec { + if in == nil { + return nil + } + out := new(LogicalClusterMigrationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogicalClusterMigrationStatus) DeepCopyInto(out *LogicalClusterMigrationStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(conditionsv1alpha1.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalClusterMigrationStatus. +func (in *LogicalClusterMigrationStatus) DeepCopy() *LogicalClusterMigrationStatus { + if in == nil { + return nil + } + out := new(LogicalClusterMigrationStatus) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.model_name.go b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.model_name.go new file mode 100644 index 00000000000..0a1b107b92a --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/migration/v1alpha1/zz_generated.model_name.go @@ -0,0 +1,62 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1alpha1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in EtcdEntry) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.EtcdEntry" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LogicalClusterDump) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.LogicalClusterDump" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LogicalClusterDumpSpec) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.LogicalClusterDumpSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LogicalClusterDumpStatus) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.LogicalClusterDumpStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LogicalClusterMigration) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.LogicalClusterMigration" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LogicalClusterMigrationList) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.LogicalClusterMigrationList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LogicalClusterMigrationSpec) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.LogicalClusterMigrationSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LogicalClusterMigrationStatus) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.migration.v1alpha1.LogicalClusterMigrationStatus" +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigration.go new file mode 100644 index 00000000000..6c40380cf56 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigration.go @@ -0,0 +1,245 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + + v1 "github.com/kcp-dev/sdk/client/applyconfiguration/meta/v1" +) + +// LogicalClusterMigrationApplyConfiguration represents a declarative configuration of the LogicalClusterMigration type for use +// with apply. +// +// LogicalClusterMigration describes a migration of a logical cluster from one shard to another. +type LogicalClusterMigrationApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *LogicalClusterMigrationSpecApplyConfiguration `json:"spec,omitempty"` + Status *LogicalClusterMigrationStatusApplyConfiguration `json:"status,omitempty"` +} + +// LogicalClusterMigration constructs a declarative configuration of the LogicalClusterMigration type for use with +// apply. +func LogicalClusterMigration(name string) *LogicalClusterMigrationApplyConfiguration { + b := &LogicalClusterMigrationApplyConfiguration{} + b.WithName(name) + b.WithKind("LogicalClusterMigration") + b.WithAPIVersion("migration.kcp.io/v1alpha1") + return b +} + +func (b LogicalClusterMigrationApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithKind(value string) *LogicalClusterMigrationApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithAPIVersion(value string) *LogicalClusterMigrationApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithName(value string) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithGenerateName(value string) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithNamespace(value string) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithUID(value types.UID) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithResourceVersion(value string) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithGeneration(value int64) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithCreationTimestamp(value metav1.Time) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *LogicalClusterMigrationApplyConfiguration) WithLabels(entries map[string]string) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *LogicalClusterMigrationApplyConfiguration) WithAnnotations(entries map[string]string) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *LogicalClusterMigrationApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *LogicalClusterMigrationApplyConfiguration) WithFinalizers(values ...string) *LogicalClusterMigrationApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *LogicalClusterMigrationApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithSpec(value *LogicalClusterMigrationSpecApplyConfiguration) *LogicalClusterMigrationApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *LogicalClusterMigrationApplyConfiguration) WithStatus(value *LogicalClusterMigrationStatusApplyConfiguration) *LogicalClusterMigrationApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *LogicalClusterMigrationApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *LogicalClusterMigrationApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *LogicalClusterMigrationApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *LogicalClusterMigrationApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationspec.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationspec.go new file mode 100644 index 00000000000..43da8b29a8a --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationspec.go @@ -0,0 +1,52 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// LogicalClusterMigrationSpecApplyConfiguration represents a declarative configuration of the LogicalClusterMigrationSpec type for use +// with apply. +// +// LogicalClusterMigrationSpec holds the desired state of the migration. +type LogicalClusterMigrationSpecApplyConfiguration struct { + // logicalCluster is the name of the logical cluster to migrate. + LogicalCluster *string `json:"logicalCluster,omitempty"` + // destinationShard is the name of the shard to migrate the logical cluster to. + DestinationShard *string `json:"destinationShard,omitempty"` +} + +// LogicalClusterMigrationSpecApplyConfiguration constructs a declarative configuration of the LogicalClusterMigrationSpec type for use with +// apply. +func LogicalClusterMigrationSpec() *LogicalClusterMigrationSpecApplyConfiguration { + return &LogicalClusterMigrationSpecApplyConfiguration{} +} + +// WithLogicalCluster sets the LogicalCluster field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LogicalCluster field is set to the value of the last call. +func (b *LogicalClusterMigrationSpecApplyConfiguration) WithLogicalCluster(value string) *LogicalClusterMigrationSpecApplyConfiguration { + b.LogicalCluster = &value + return b +} + +// WithDestinationShard sets the DestinationShard field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DestinationShard field is set to the value of the last call. +func (b *LogicalClusterMigrationSpecApplyConfiguration) WithDestinationShard(value string) *LogicalClusterMigrationSpecApplyConfiguration { + b.DestinationShard = &value + return b +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationstatus.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationstatus.go new file mode 100644 index 00000000000..8ecf58213b0 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1/logicalclustermigrationstatus.go @@ -0,0 +1,68 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" +) + +// LogicalClusterMigrationStatusApplyConfiguration represents a declarative configuration of the LogicalClusterMigrationStatus type for use +// with apply. +// +// LogicalClusterMigrationStatus communicates the observed state of the migration. +type LogicalClusterMigrationStatusApplyConfiguration struct { + // phase is the current phase of the migration. + Phase *migrationv1alpha1.LogicalClusterMigrationPhaseType `json:"phase,omitempty"` + // originShard is the name of the shard to migrate the logical cluster from. + // Set by the origin shard controller during preparation. + OriginShard *string `json:"originShard,omitempty"` + // Current processing state of the migration. + Conditions *conditionsv1alpha1.Conditions `json:"conditions,omitempty"` +} + +// LogicalClusterMigrationStatusApplyConfiguration constructs a declarative configuration of the LogicalClusterMigrationStatus type for use with +// apply. +func LogicalClusterMigrationStatus() *LogicalClusterMigrationStatusApplyConfiguration { + return &LogicalClusterMigrationStatusApplyConfiguration{} +} + +// WithPhase sets the Phase field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Phase field is set to the value of the last call. +func (b *LogicalClusterMigrationStatusApplyConfiguration) WithPhase(value migrationv1alpha1.LogicalClusterMigrationPhaseType) *LogicalClusterMigrationStatusApplyConfiguration { + b.Phase = &value + return b +} + +// WithOriginShard sets the OriginShard field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OriginShard field is set to the value of the last call. +func (b *LogicalClusterMigrationStatusApplyConfiguration) WithOriginShard(value string) *LogicalClusterMigrationStatusApplyConfiguration { + b.OriginShard = &value + return b +} + +// WithConditions sets the Conditions field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Conditions field is set to the value of the last call. +func (b *LogicalClusterMigrationStatusApplyConfiguration) WithConditions(value conditionsv1alpha1.Conditions) *LogicalClusterMigrationStatusApplyConfiguration { + b.Conditions = &value + return b +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go index c983fb81416..0fca2fb9345 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go @@ -29,6 +29,7 @@ import ( v1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" @@ -39,6 +40,7 @@ import ( applyconfigurationcorev1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/core/v1alpha1" internal "github.com/kcp-dev/sdk/client/applyconfiguration/internal" applyconfigurationmetav1 "github.com/kcp-dev/sdk/client/applyconfiguration/meta/v1" + applyconfigurationmigrationv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1" applyconfigurationtenancyv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/tenancy/v1alpha1" applyconfigurationtopologyv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/topology/v1alpha1" ) @@ -221,6 +223,14 @@ func ForKind(kind schema.GroupVersionKind) interface{} { case v1.SchemeGroupVersion.WithKind("TypeMeta"): return &applyconfigurationmetav1.TypeMetaApplyConfiguration{} + // Group=migration.kcp.io, Version=v1alpha1 + case migrationv1alpha1.SchemeGroupVersion.WithKind("LogicalClusterMigration"): + return &applyconfigurationmigrationv1alpha1.LogicalClusterMigrationApplyConfiguration{} + case migrationv1alpha1.SchemeGroupVersion.WithKind("LogicalClusterMigrationSpec"): + return &applyconfigurationmigrationv1alpha1.LogicalClusterMigrationSpecApplyConfiguration{} + case migrationv1alpha1.SchemeGroupVersion.WithKind("LogicalClusterMigrationStatus"): + return &applyconfigurationmigrationv1alpha1.LogicalClusterMigrationStatusApplyConfiguration{} + // Group=tenancy.kcp.io, Version=v1alpha1 case tenancyv1alpha1.SchemeGroupVersion.WithKind("APIExportReference"): return &applyconfigurationtenancyv1alpha1.APIExportReferenceApplyConfiguration{} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go index 27a78e5f3d0..dd061114ab8 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go @@ -30,6 +30,7 @@ import ( apisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/tenancy/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/topology/v1alpha1" ) @@ -40,6 +41,7 @@ type Interface interface { ApisV1alpha2() apisv1alpha2.ApisV1alpha2Interface CacheV1alpha1() cachev1alpha1.CacheV1alpha1Interface CoreV1alpha1() corev1alpha1.CoreV1alpha1Interface + MigrationV1alpha1() migrationv1alpha1.MigrationV1alpha1Interface TenancyV1alpha1() tenancyv1alpha1.TenancyV1alpha1Interface TopologyV1alpha1() topologyv1alpha1.TopologyV1alpha1Interface } @@ -47,12 +49,13 @@ type Interface interface { // Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient - apisV1alpha1 *apisv1alpha1.ApisV1alpha1Client - apisV1alpha2 *apisv1alpha2.ApisV1alpha2Client - cacheV1alpha1 *cachev1alpha1.CacheV1alpha1Client - coreV1alpha1 *corev1alpha1.CoreV1alpha1Client - tenancyV1alpha1 *tenancyv1alpha1.TenancyV1alpha1Client - topologyV1alpha1 *topologyv1alpha1.TopologyV1alpha1Client + apisV1alpha1 *apisv1alpha1.ApisV1alpha1Client + apisV1alpha2 *apisv1alpha2.ApisV1alpha2Client + cacheV1alpha1 *cachev1alpha1.CacheV1alpha1Client + coreV1alpha1 *corev1alpha1.CoreV1alpha1Client + migrationV1alpha1 *migrationv1alpha1.MigrationV1alpha1Client + tenancyV1alpha1 *tenancyv1alpha1.TenancyV1alpha1Client + topologyV1alpha1 *topologyv1alpha1.TopologyV1alpha1Client } // ApisV1alpha1 retrieves the ApisV1alpha1Client @@ -75,6 +78,11 @@ func (c *Clientset) CoreV1alpha1() corev1alpha1.CoreV1alpha1Interface { return c.coreV1alpha1 } +// MigrationV1alpha1 retrieves the MigrationV1alpha1Client +func (c *Clientset) MigrationV1alpha1() migrationv1alpha1.MigrationV1alpha1Interface { + return c.migrationV1alpha1 +} + // TenancyV1alpha1 retrieves the TenancyV1alpha1Client func (c *Clientset) TenancyV1alpha1() tenancyv1alpha1.TenancyV1alpha1Interface { return c.tenancyV1alpha1 @@ -145,6 +153,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } + cs.migrationV1alpha1, err = migrationv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.tenancyV1alpha1, err = tenancyv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -178,6 +190,7 @@ func New(c rest.Interface) *Clientset { cs.apisV1alpha2 = apisv1alpha2.New(c) cs.cacheV1alpha1 = cachev1alpha1.New(c) cs.coreV1alpha1 = corev1alpha1.New(c) + cs.migrationV1alpha1 = migrationv1alpha1.New(c) cs.tenancyV1alpha1 = tenancyv1alpha1.New(c) cs.topologyV1alpha1 = topologyv1alpha1.New(c) diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go index 82803628c87..f79a81cf9b6 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go @@ -33,6 +33,7 @@ import ( apisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/tenancy/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/topology/v1alpha1" ) @@ -44,6 +45,7 @@ type ClusterInterface interface { ApisV1alpha2() apisv1alpha2.ApisV1alpha2ClusterInterface CacheV1alpha1() cachev1alpha1.CacheV1alpha1ClusterInterface CoreV1alpha1() corev1alpha1.CoreV1alpha1ClusterInterface + MigrationV1alpha1() migrationv1alpha1.MigrationV1alpha1ClusterInterface TenancyV1alpha1() tenancyv1alpha1.TenancyV1alpha1ClusterInterface TopologyV1alpha1() topologyv1alpha1.TopologyV1alpha1ClusterInterface } @@ -51,13 +53,14 @@ type ClusterInterface interface { // ClusterClientset contains the cluster clients for groups. type ClusterClientset struct { *discovery.DiscoveryClient - clientCache kcpclient.Cache[*client.Clientset] - apisV1alpha1 *apisv1alpha1.ApisV1alpha1ClusterClient - apisV1alpha2 *apisv1alpha2.ApisV1alpha2ClusterClient - cacheV1alpha1 *cachev1alpha1.CacheV1alpha1ClusterClient - coreV1alpha1 *corev1alpha1.CoreV1alpha1ClusterClient - tenancyV1alpha1 *tenancyv1alpha1.TenancyV1alpha1ClusterClient - topologyV1alpha1 *topologyv1alpha1.TopologyV1alpha1ClusterClient + clientCache kcpclient.Cache[*client.Clientset] + apisV1alpha1 *apisv1alpha1.ApisV1alpha1ClusterClient + apisV1alpha2 *apisv1alpha2.ApisV1alpha2ClusterClient + cacheV1alpha1 *cachev1alpha1.CacheV1alpha1ClusterClient + coreV1alpha1 *corev1alpha1.CoreV1alpha1ClusterClient + migrationV1alpha1 *migrationv1alpha1.MigrationV1alpha1ClusterClient + tenancyV1alpha1 *tenancyv1alpha1.TenancyV1alpha1ClusterClient + topologyV1alpha1 *topologyv1alpha1.TopologyV1alpha1ClusterClient } // Discovery retrieves the DiscoveryClient. @@ -88,6 +91,11 @@ func (c *ClusterClientset) CoreV1alpha1() corev1alpha1.CoreV1alpha1ClusterInterf return c.coreV1alpha1 } +// MigrationV1alpha1 retrieves the MigrationV1alpha1ClusterClient. +func (c *ClusterClientset) MigrationV1alpha1() migrationv1alpha1.MigrationV1alpha1ClusterInterface { + return c.migrationV1alpha1 +} + // TenancyV1alpha1 retrieves the TenancyV1alpha1ClusterClient. func (c *ClusterClientset) TenancyV1alpha1() tenancyv1alpha1.TenancyV1alpha1ClusterInterface { return c.tenancyV1alpha1 @@ -166,6 +174,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*ClusterCli if err != nil { return nil, err } + cs.migrationV1alpha1, err = migrationv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.tenancyV1alpha1, err = tenancyv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -199,6 +211,7 @@ func New(c *rest.Config) *ClusterClientset { cs.apisV1alpha2 = apisv1alpha2.NewForConfigOrDie(c) cs.cacheV1alpha1 = cachev1alpha1.NewForConfigOrDie(c) cs.coreV1alpha1 = corev1alpha1.NewForConfigOrDie(c) + cs.migrationV1alpha1 = migrationv1alpha1.NewForConfigOrDie(c) cs.tenancyV1alpha1 = tenancyv1alpha1.NewForConfigOrDie(c) cs.topologyV1alpha1 = topologyv1alpha1.NewForConfigOrDie(c) diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go index a2bb8cf91b7..9b1532c35e9 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go @@ -37,6 +37,8 @@ import ( kcpfakecachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/cache/v1alpha1/fake" kcpcorev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/core/v1alpha1" kcpfakecorev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/core/v1alpha1/fake" + kcpmigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1" + kcpfakemigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake" kcptenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/tenancy/v1alpha1" kcpfaketenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/tenancy/v1alpha1/fake" kcptopologyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/topology/v1alpha1" @@ -45,6 +47,7 @@ import ( apisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/tenancy/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/topology/v1alpha1" ) @@ -142,6 +145,11 @@ func (c *ClusterClientset) CoreV1alpha1() kcpcorev1alpha1.CoreV1alpha1ClusterInt return &kcpfakecorev1alpha1.CoreV1alpha1ClusterClient{Fake: &c.Fake} } +// MigrationV1alpha1 retrieves the MigrationV1alpha1ClusterClient +func (c *ClusterClientset) MigrationV1alpha1() kcpmigrationv1alpha1.MigrationV1alpha1ClusterInterface { + return &kcpfakemigrationv1alpha1.MigrationV1alpha1ClusterClient{Fake: &c.Fake} +} + // TenancyV1alpha1 retrieves the TenancyV1alpha1ClusterClient func (c *ClusterClientset) TenancyV1alpha1() kcptenancyv1alpha1.TenancyV1alpha1ClusterInterface { return &kcpfaketenancyv1alpha1.TenancyV1alpha1ClusterClient{Fake: &c.Fake} @@ -215,6 +223,11 @@ func (c *Clientset) CoreV1alpha1() corev1alpha1.CoreV1alpha1Interface { return &kcpfakecorev1alpha1.CoreV1alpha1Client{Fake: c.Fake, ClusterPath: c.clusterPath} } +// MigrationV1alpha1 retrieves the MigrationV1alpha1Client +func (c *Clientset) MigrationV1alpha1() migrationv1alpha1.MigrationV1alpha1Interface { + return &kcpfakemigrationv1alpha1.MigrationV1alpha1Client{Fake: c.Fake, ClusterPath: c.clusterPath} +} + // TenancyV1alpha1 retrieves the TenancyV1alpha1Client func (c *Clientset) TenancyV1alpha1() tenancyv1alpha1.TenancyV1alpha1Interface { return &kcpfaketenancyv1alpha1.TenancyV1alpha1Client{Fake: c.Fake, ClusterPath: c.clusterPath} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go index e4971a19a37..9701248653e 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go @@ -29,6 +29,7 @@ import ( apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" ) @@ -41,6 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, corev1alpha1.AddToScheme, + migrationv1alpha1.AddToScheme, tenancyv1alpha1.AddToScheme, topologyv1alpha1.AddToScheme, } diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go index d0e00190489..f2981eeee15 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go @@ -29,6 +29,7 @@ import ( apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" ) @@ -41,6 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, corev1alpha1.AddToScheme, + migrationv1alpha1.AddToScheme, tenancyv1alpha1.AddToScheme, topologyv1alpha1.AddToScheme, } diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/doc.go new file mode 100644 index 00000000000..3e873237924 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/doc.go new file mode 100644 index 00000000000..100cd63c05b --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +// Package fake has the automatically generated cluster clients. +package fake diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclusterdump.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclusterdump.go new file mode 100644 index 00000000000..d2671eb6354 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclusterdump.go @@ -0,0 +1,72 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package fake + +import ( + kcpgentype "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/gentype" + kcptesting "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/testing" + "github.com/kcp-dev/logicalcluster/v3" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + typedkcpmigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1" + typedmigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +// logicalClusterDumpClusterClient implements LogicalClusterDumpClusterInterface +type logicalClusterDumpClusterClient struct { + *kcpgentype.FakeClusterClient[*migrationv1alpha1.LogicalClusterDump] + Fake *kcptesting.Fake +} + +func newFakeLogicalClusterDumpClusterClient(fake *MigrationV1alpha1ClusterClient) typedkcpmigrationv1alpha1.LogicalClusterDumpClusterInterface { + return &logicalClusterDumpClusterClient{ + kcpgentype.NewFakeClusterClient[*migrationv1alpha1.LogicalClusterDump]( + fake.Fake, + migrationv1alpha1.SchemeGroupVersion.WithResource("logicalclusterdumps"), + migrationv1alpha1.SchemeGroupVersion.WithKind("LogicalClusterDump"), + func() *migrationv1alpha1.LogicalClusterDump { return &migrationv1alpha1.LogicalClusterDump{} }, + ), + fake.Fake, + } +} + +func (c *logicalClusterDumpClusterClient) Cluster(cluster logicalcluster.Path) typedmigrationv1alpha1.LogicalClusterDumpInterface { + return newFakeLogicalClusterDumpClient(c.Fake, cluster) +} + +// logicalClusterDumpScopedClient implements LogicalClusterDumpInterface +type logicalClusterDumpScopedClient struct { + *kcpgentype.FakeClient[*migrationv1alpha1.LogicalClusterDump] + Fake *kcptesting.Fake + ClusterPath logicalcluster.Path +} + +func newFakeLogicalClusterDumpClient(fake *kcptesting.Fake, clusterPath logicalcluster.Path) typedmigrationv1alpha1.LogicalClusterDumpInterface { + return &logicalClusterDumpScopedClient{ + kcpgentype.NewFakeClient[*migrationv1alpha1.LogicalClusterDump]( + fake, + clusterPath, + "", + migrationv1alpha1.SchemeGroupVersion.WithResource("logicalclusterdumps"), + migrationv1alpha1.SchemeGroupVersion.WithKind("LogicalClusterDump"), + func() *migrationv1alpha1.LogicalClusterDump { return &migrationv1alpha1.LogicalClusterDump{} }, + ), + fake, + clusterPath, + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclustermigration.go new file mode 100644 index 00000000000..6fff0735308 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/logicalclustermigration.go @@ -0,0 +1,93 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package fake + +import ( + kcpgentype "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/gentype" + kcptesting "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/testing" + "github.com/kcp-dev/logicalcluster/v3" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1" + typedkcpmigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1" + typedmigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +// logicalClusterMigrationClusterClient implements LogicalClusterMigrationClusterInterface +type logicalClusterMigrationClusterClient struct { + *kcpgentype.FakeClusterClientWithList[*migrationv1alpha1.LogicalClusterMigration, *migrationv1alpha1.LogicalClusterMigrationList] + Fake *kcptesting.Fake +} + +func newFakeLogicalClusterMigrationClusterClient(fake *MigrationV1alpha1ClusterClient) typedkcpmigrationv1alpha1.LogicalClusterMigrationClusterInterface { + return &logicalClusterMigrationClusterClient{ + kcpgentype.NewFakeClusterClientWithList[*migrationv1alpha1.LogicalClusterMigration, *migrationv1alpha1.LogicalClusterMigrationList]( + fake.Fake, + migrationv1alpha1.SchemeGroupVersion.WithResource("logicalclustermigrations"), + migrationv1alpha1.SchemeGroupVersion.WithKind("LogicalClusterMigration"), + func() *migrationv1alpha1.LogicalClusterMigration { return &migrationv1alpha1.LogicalClusterMigration{} }, + func() *migrationv1alpha1.LogicalClusterMigrationList { + return &migrationv1alpha1.LogicalClusterMigrationList{} + }, + func(dst, src *migrationv1alpha1.LogicalClusterMigrationList) { dst.ListMeta = src.ListMeta }, + func(list *migrationv1alpha1.LogicalClusterMigrationList) []*migrationv1alpha1.LogicalClusterMigration { + return kcpgentype.ToPointerSlice(list.Items) + }, + func(list *migrationv1alpha1.LogicalClusterMigrationList, items []*migrationv1alpha1.LogicalClusterMigration) { + list.Items = kcpgentype.FromPointerSlice(items) + }, + ), + fake.Fake, + } +} + +func (c *logicalClusterMigrationClusterClient) Cluster(cluster logicalcluster.Path) typedmigrationv1alpha1.LogicalClusterMigrationInterface { + return newFakeLogicalClusterMigrationClient(c.Fake, cluster) +} + +// logicalClusterMigrationScopedClient implements LogicalClusterMigrationInterface +type logicalClusterMigrationScopedClient struct { + *kcpgentype.FakeClientWithListAndApply[*migrationv1alpha1.LogicalClusterMigration, *migrationv1alpha1.LogicalClusterMigrationList, *kcpv1alpha1.LogicalClusterMigrationApplyConfiguration] + Fake *kcptesting.Fake + ClusterPath logicalcluster.Path +} + +func newFakeLogicalClusterMigrationClient(fake *kcptesting.Fake, clusterPath logicalcluster.Path) typedmigrationv1alpha1.LogicalClusterMigrationInterface { + return &logicalClusterMigrationScopedClient{ + kcpgentype.NewFakeClientWithListAndApply[*migrationv1alpha1.LogicalClusterMigration, *migrationv1alpha1.LogicalClusterMigrationList, *kcpv1alpha1.LogicalClusterMigrationApplyConfiguration]( + fake, + clusterPath, + "", + migrationv1alpha1.SchemeGroupVersion.WithResource("logicalclustermigrations"), + migrationv1alpha1.SchemeGroupVersion.WithKind("LogicalClusterMigration"), + func() *migrationv1alpha1.LogicalClusterMigration { return &migrationv1alpha1.LogicalClusterMigration{} }, + func() *migrationv1alpha1.LogicalClusterMigrationList { + return &migrationv1alpha1.LogicalClusterMigrationList{} + }, + func(dst, src *migrationv1alpha1.LogicalClusterMigrationList) { dst.ListMeta = src.ListMeta }, + func(list *migrationv1alpha1.LogicalClusterMigrationList) []*migrationv1alpha1.LogicalClusterMigration { + return kcpgentype.ToPointerSlice(list.Items) + }, + func(list *migrationv1alpha1.LogicalClusterMigrationList, items []*migrationv1alpha1.LogicalClusterMigration) { + list.Items = kcpgentype.FromPointerSlice(items) + }, + ), + fake, + clusterPath, + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/migration_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/migration_client.go new file mode 100644 index 00000000000..4f00d76d854 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/fake/migration_client.go @@ -0,0 +1,69 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + + kcptesting "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/testing" + "github.com/kcp-dev/logicalcluster/v3" + kcpmigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +var _ kcpmigrationv1alpha1.MigrationV1alpha1ClusterInterface = (*MigrationV1alpha1ClusterClient)(nil) + +type MigrationV1alpha1ClusterClient struct { + *kcptesting.Fake +} + +func (c *MigrationV1alpha1ClusterClient) Cluster(clusterPath logicalcluster.Path) migrationv1alpha1.MigrationV1alpha1Interface { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + return &MigrationV1alpha1Client{Fake: c.Fake, ClusterPath: clusterPath} +} + +func (c *MigrationV1alpha1ClusterClient) LogicalClusterDumps() kcpmigrationv1alpha1.LogicalClusterDumpClusterInterface { + return newFakeLogicalClusterDumpClusterClient(c) +} + +func (c *MigrationV1alpha1ClusterClient) LogicalClusterMigrations() kcpmigrationv1alpha1.LogicalClusterMigrationClusterInterface { + return newFakeLogicalClusterMigrationClusterClient(c) +} + +type MigrationV1alpha1Client struct { + *kcptesting.Fake + ClusterPath logicalcluster.Path +} + +func (c *MigrationV1alpha1Client) LogicalClusterDumps() migrationv1alpha1.LogicalClusterDumpInterface { + return newFakeLogicalClusterDumpClient(c.Fake, c.ClusterPath) +} + +func (c *MigrationV1alpha1Client) LogicalClusterMigrations() migrationv1alpha1.LogicalClusterMigrationInterface { + return newFakeLogicalClusterMigrationClient(c.Fake, c.ClusterPath) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *MigrationV1alpha1Client) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/generated_expansion.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/generated_expansion.go new file mode 100644 index 00000000000..8a111b7797b --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/generated_expansion.go @@ -0,0 +1,23 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package v1alpha1 + +type LogicalClusterDumpClusterExpansion interface{} + +type LogicalClusterMigrationClusterExpansion interface{} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclusterdump.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclusterdump.go new file mode 100644 index 00000000000..51b2be483bc --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclusterdump.go @@ -0,0 +1,52 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" + "github.com/kcp-dev/logicalcluster/v3" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +// LogicalClusterDumpsClusterGetter has a method to return a LogicalClusterDumpClusterInterface. +// A group's cluster client should implement this interface. +type LogicalClusterDumpsClusterGetter interface { + LogicalClusterDumps() LogicalClusterDumpClusterInterface +} + +// LogicalClusterDumpClusterInterface can operate on LogicalClusterDumps across all clusters, +// or scope down to one cluster and return a kcpv1alpha1.LogicalClusterDumpInterface. +type LogicalClusterDumpClusterInterface interface { + Cluster(logicalcluster.Path) kcpv1alpha1.LogicalClusterDumpInterface + + LogicalClusterDumpClusterExpansion +} + +type logicalClusterDumpsClusterInterface struct { + clientCache kcpclient.Cache[*kcpv1alpha1.MigrationV1alpha1Client] +} + +// Cluster scopes the client down to a particular cluster. +func (c *logicalClusterDumpsClusterInterface) Cluster(clusterPath logicalcluster.Path) kcpv1alpha1.LogicalClusterDumpInterface { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + + return c.clientCache.ClusterOrDie(clusterPath).LogicalClusterDumps() +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclustermigration.go new file mode 100644 index 00000000000..b42f11f5f64 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/logicalclustermigration.go @@ -0,0 +1,69 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + watch "k8s.io/apimachinery/pkg/watch" + + kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" + "github.com/kcp-dev/logicalcluster/v3" + kcpmigrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +// LogicalClusterMigrationsClusterGetter has a method to return a LogicalClusterMigrationClusterInterface. +// A group's cluster client should implement this interface. +type LogicalClusterMigrationsClusterGetter interface { + LogicalClusterMigrations() LogicalClusterMigrationClusterInterface +} + +// LogicalClusterMigrationClusterInterface can operate on LogicalClusterMigrations across all clusters, +// or scope down to one cluster and return a kcpv1alpha1.LogicalClusterMigrationInterface. +type LogicalClusterMigrationClusterInterface interface { + Cluster(logicalcluster.Path) kcpv1alpha1.LogicalClusterMigrationInterface + List(ctx context.Context, opts v1.ListOptions) (*kcpmigrationv1alpha1.LogicalClusterMigrationList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + LogicalClusterMigrationClusterExpansion +} + +type logicalClusterMigrationsClusterInterface struct { + clientCache kcpclient.Cache[*kcpv1alpha1.MigrationV1alpha1Client] +} + +// Cluster scopes the client down to a particular cluster. +func (c *logicalClusterMigrationsClusterInterface) Cluster(clusterPath logicalcluster.Path) kcpv1alpha1.LogicalClusterMigrationInterface { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + + return c.clientCache.ClusterOrDie(clusterPath).LogicalClusterMigrations() +} + +// List returns the entire collection of all LogicalClusterMigrations across all clusters. +func (c *logicalClusterMigrationsClusterInterface) List(ctx context.Context, opts v1.ListOptions) (*kcpmigrationv1alpha1.LogicalClusterMigrationList, error) { + return c.clientCache.ClusterOrDie(logicalcluster.Wildcard).LogicalClusterMigrations().List(ctx, opts) +} + +// Watch begins to watch all LogicalClusterMigrations across all clusters. +func (c *logicalClusterMigrationsClusterInterface) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.clientCache.ClusterOrDie(logicalcluster.Wildcard).LogicalClusterMigrations().Watch(ctx, opts) +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/migration_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/migration_client.go new file mode 100644 index 00000000000..e8db386810c --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/migration/v1alpha1/migration_client.go @@ -0,0 +1,108 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + http "net/http" + + rest "k8s.io/client-go/rest" + + kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" + "github.com/kcp-dev/logicalcluster/v3" + kcpmigrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + kcpscheme "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +type MigrationV1alpha1ClusterInterface interface { + MigrationV1alpha1ClusterScoper + LogicalClusterDumpsClusterGetter + LogicalClusterMigrationsClusterGetter +} + +type MigrationV1alpha1ClusterScoper interface { + Cluster(logicalcluster.Path) kcpv1alpha1.MigrationV1alpha1Interface +} + +// MigrationV1alpha1ClusterClient is used to interact with features provided by the migration.kcp.io group. +type MigrationV1alpha1ClusterClient struct { + clientCache kcpclient.Cache[*kcpv1alpha1.MigrationV1alpha1Client] +} + +func (c *MigrationV1alpha1ClusterClient) Cluster(clusterPath logicalcluster.Path) kcpv1alpha1.MigrationV1alpha1Interface { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + return c.clientCache.ClusterOrDie(clusterPath) +} + +func (c *MigrationV1alpha1ClusterClient) LogicalClusterDumps() LogicalClusterDumpClusterInterface { + return &logicalClusterDumpsClusterInterface{clientCache: c.clientCache} +} + +func (c *MigrationV1alpha1ClusterClient) LogicalClusterMigrations() LogicalClusterMigrationClusterInterface { + return &logicalClusterMigrationsClusterInterface{clientCache: c.clientCache} +} + +// NewForConfig creates a new MigrationV1alpha1ClusterClient for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*MigrationV1alpha1ClusterClient, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new MigrationV1alpha1ClusterClient for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*MigrationV1alpha1ClusterClient, error) { + cache := kcpclient.NewCache(c, h, &kcpclient.Constructor[*kcpv1alpha1.MigrationV1alpha1Client]{ + NewForConfigAndClient: kcpv1alpha1.NewForConfigAndClient, + }) + if _, err := cache.Cluster(logicalcluster.Name("root").Path()); err != nil { + return nil, err + } + + return &MigrationV1alpha1ClusterClient{clientCache: cache}, nil +} + +// NewForConfigOrDie creates a new MigrationV1alpha1ClusterClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *MigrationV1alpha1ClusterClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +func setConfigDefaults(config *rest.Config) { + gv := kcpmigrationv1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(kcpscheme.Scheme, kcpscheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go index 5aa90edf6c2..f5e771300df 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go @@ -36,6 +36,8 @@ import ( fakecachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/cache/v1alpha1/fake" corev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/core/v1alpha1" fakecorev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/core/v1alpha1/fake" + migrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" + fakemigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake" tenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/tenancy/v1alpha1" faketenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/tenancy/v1alpha1/fake" topologyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/topology/v1alpha1" @@ -167,6 +169,11 @@ func (c *Clientset) CoreV1alpha1() corev1alpha1.CoreV1alpha1Interface { return &fakecorev1alpha1.FakeCoreV1alpha1{Fake: &c.Fake} } +// MigrationV1alpha1 retrieves the MigrationV1alpha1Client +func (c *Clientset) MigrationV1alpha1() migrationv1alpha1.MigrationV1alpha1Interface { + return &fakemigrationv1alpha1.FakeMigrationV1alpha1{Fake: &c.Fake} +} + // TenancyV1alpha1 retrieves the TenancyV1alpha1Client func (c *Clientset) TenancyV1alpha1() tenancyv1alpha1.TenancyV1alpha1Interface { return &faketenancyv1alpha1.FakeTenancyV1alpha1{Fake: &c.Fake} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go index 8293dab4ea8..4656aa8f03a 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go @@ -29,6 +29,7 @@ import ( apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" ) @@ -41,6 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, corev1alpha1.AddToScheme, + migrationv1alpha1.AddToScheme, tenancyv1alpha1.AddToScheme, topologyv1alpha1.AddToScheme, } diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go index eb79b75ae43..f86bd546c4f 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go @@ -29,6 +29,7 @@ import ( apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" ) @@ -41,6 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, corev1alpha1.AddToScheme, + migrationv1alpha1.AddToScheme, tenancyv1alpha1.AddToScheme, topologyv1alpha1.AddToScheme, } diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/doc.go new file mode 100644 index 00000000000..2be316615e0 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/doc.go new file mode 100644 index 00000000000..59e4de8efe6 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclusterdump.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclusterdump.go new file mode 100644 index 00000000000..10287c63cad --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclusterdump.go @@ -0,0 +1,45 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + gentype "k8s.io/client-go/gentype" + + v1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +// fakeLogicalClusterDumps implements LogicalClusterDumpInterface +type fakeLogicalClusterDumps struct { + *gentype.FakeClient[*v1alpha1.LogicalClusterDump] + Fake *FakeMigrationV1alpha1 +} + +func newFakeLogicalClusterDumps(fake *FakeMigrationV1alpha1) migrationv1alpha1.LogicalClusterDumpInterface { + return &fakeLogicalClusterDumps{ + gentype.NewFakeClient[*v1alpha1.LogicalClusterDump]( + fake.Fake, + "", + v1alpha1.SchemeGroupVersion.WithResource("logicalclusterdumps"), + v1alpha1.SchemeGroupVersion.WithKind("LogicalClusterDump"), + func() *v1alpha1.LogicalClusterDump { return &v1alpha1.LogicalClusterDump{} }, + ), + fake, + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclustermigration.go new file mode 100644 index 00000000000..ccb504389a2 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_logicalclustermigration.go @@ -0,0 +1,54 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + gentype "k8s.io/client-go/gentype" + + v1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1" + typedmigrationv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +// fakeLogicalClusterMigrations implements LogicalClusterMigrationInterface +type fakeLogicalClusterMigrations struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.LogicalClusterMigration, *v1alpha1.LogicalClusterMigrationList, *migrationv1alpha1.LogicalClusterMigrationApplyConfiguration] + Fake *FakeMigrationV1alpha1 +} + +func newFakeLogicalClusterMigrations(fake *FakeMigrationV1alpha1) typedmigrationv1alpha1.LogicalClusterMigrationInterface { + return &fakeLogicalClusterMigrations{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.LogicalClusterMigration, *v1alpha1.LogicalClusterMigrationList, *migrationv1alpha1.LogicalClusterMigrationApplyConfiguration]( + fake.Fake, + "", + v1alpha1.SchemeGroupVersion.WithResource("logicalclustermigrations"), + v1alpha1.SchemeGroupVersion.WithKind("LogicalClusterMigration"), + func() *v1alpha1.LogicalClusterMigration { return &v1alpha1.LogicalClusterMigration{} }, + func() *v1alpha1.LogicalClusterMigrationList { return &v1alpha1.LogicalClusterMigrationList{} }, + func(dst, src *v1alpha1.LogicalClusterMigrationList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.LogicalClusterMigrationList) []*v1alpha1.LogicalClusterMigration { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.LogicalClusterMigrationList, items []*v1alpha1.LogicalClusterMigration) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_migration_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_migration_client.go new file mode 100644 index 00000000000..8286c8b8f64 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/fake/fake_migration_client.go @@ -0,0 +1,45 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" +) + +type FakeMigrationV1alpha1 struct { + *testing.Fake +} + +func (c *FakeMigrationV1alpha1) LogicalClusterDumps() v1alpha1.LogicalClusterDumpInterface { + return newFakeLogicalClusterDumps(c) +} + +func (c *FakeMigrationV1alpha1) LogicalClusterMigrations() v1alpha1.LogicalClusterMigrationInterface { + return newFakeLogicalClusterMigrations(c) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeMigrationV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/generated_expansion.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/generated_expansion.go new file mode 100644 index 00000000000..695299dae63 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/generated_expansion.go @@ -0,0 +1,23 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type LogicalClusterDumpExpansion interface{} + +type LogicalClusterMigrationExpansion interface{} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclusterdump.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclusterdump.go new file mode 100644 index 00000000000..21cc5301293 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclusterdump.go @@ -0,0 +1,59 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gentype "k8s.io/client-go/gentype" + + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + scheme "github.com/kcp-dev/sdk/client/clientset/versioned/scheme" +) + +// LogicalClusterDumpsGetter has a method to return a LogicalClusterDumpInterface. +// A group's client should implement this interface. +type LogicalClusterDumpsGetter interface { + LogicalClusterDumps() LogicalClusterDumpInterface +} + +// LogicalClusterDumpInterface has methods to work with LogicalClusterDump resources. +type LogicalClusterDumpInterface interface { + Create(ctx context.Context, logicalClusterDump *migrationv1alpha1.LogicalClusterDump, opts v1.CreateOptions) (*migrationv1alpha1.LogicalClusterDump, error) + LogicalClusterDumpExpansion +} + +// logicalClusterDumps implements LogicalClusterDumpInterface +type logicalClusterDumps struct { + *gentype.Client[*migrationv1alpha1.LogicalClusterDump] +} + +// newLogicalClusterDumps returns a LogicalClusterDumps +func newLogicalClusterDumps(c *MigrationV1alpha1Client) *logicalClusterDumps { + return &logicalClusterDumps{ + gentype.NewClient[*migrationv1alpha1.LogicalClusterDump]( + "logicalclusterdumps", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *migrationv1alpha1.LogicalClusterDump { return &migrationv1alpha1.LogicalClusterDump{} }, + ), + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclustermigration.go new file mode 100644 index 00000000000..524936f4b39 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/logicalclustermigration.go @@ -0,0 +1,77 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" + + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + applyconfigurationmigrationv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/migration/v1alpha1" + scheme "github.com/kcp-dev/sdk/client/clientset/versioned/scheme" +) + +// LogicalClusterMigrationsGetter has a method to return a LogicalClusterMigrationInterface. +// A group's client should implement this interface. +type LogicalClusterMigrationsGetter interface { + LogicalClusterMigrations() LogicalClusterMigrationInterface +} + +// LogicalClusterMigrationInterface has methods to work with LogicalClusterMigration resources. +type LogicalClusterMigrationInterface interface { + Create(ctx context.Context, logicalClusterMigration *migrationv1alpha1.LogicalClusterMigration, opts v1.CreateOptions) (*migrationv1alpha1.LogicalClusterMigration, error) + Update(ctx context.Context, logicalClusterMigration *migrationv1alpha1.LogicalClusterMigration, opts v1.UpdateOptions) (*migrationv1alpha1.LogicalClusterMigration, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, logicalClusterMigration *migrationv1alpha1.LogicalClusterMigration, opts v1.UpdateOptions) (*migrationv1alpha1.LogicalClusterMigration, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*migrationv1alpha1.LogicalClusterMigration, error) + List(ctx context.Context, opts v1.ListOptions) (*migrationv1alpha1.LogicalClusterMigrationList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *migrationv1alpha1.LogicalClusterMigration, err error) + Apply(ctx context.Context, logicalClusterMigration *applyconfigurationmigrationv1alpha1.LogicalClusterMigrationApplyConfiguration, opts v1.ApplyOptions) (result *migrationv1alpha1.LogicalClusterMigration, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, logicalClusterMigration *applyconfigurationmigrationv1alpha1.LogicalClusterMigrationApplyConfiguration, opts v1.ApplyOptions) (result *migrationv1alpha1.LogicalClusterMigration, err error) + LogicalClusterMigrationExpansion +} + +// logicalClusterMigrations implements LogicalClusterMigrationInterface +type logicalClusterMigrations struct { + *gentype.ClientWithListAndApply[*migrationv1alpha1.LogicalClusterMigration, *migrationv1alpha1.LogicalClusterMigrationList, *applyconfigurationmigrationv1alpha1.LogicalClusterMigrationApplyConfiguration] +} + +// newLogicalClusterMigrations returns a LogicalClusterMigrations +func newLogicalClusterMigrations(c *MigrationV1alpha1Client) *logicalClusterMigrations { + return &logicalClusterMigrations{ + gentype.NewClientWithListAndApply[*migrationv1alpha1.LogicalClusterMigration, *migrationv1alpha1.LogicalClusterMigrationList, *applyconfigurationmigrationv1alpha1.LogicalClusterMigrationApplyConfiguration]( + "logicalclustermigrations", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *migrationv1alpha1.LogicalClusterMigration { return &migrationv1alpha1.LogicalClusterMigration{} }, + func() *migrationv1alpha1.LogicalClusterMigrationList { + return &migrationv1alpha1.LogicalClusterMigrationList{} + }, + ), + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/migration_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/migration_client.go new file mode 100644 index 00000000000..a2d0b98b6eb --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1/migration_client.go @@ -0,0 +1,107 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + http "net/http" + + rest "k8s.io/client-go/rest" + + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + scheme "github.com/kcp-dev/sdk/client/clientset/versioned/scheme" +) + +type MigrationV1alpha1Interface interface { + RESTClient() rest.Interface + LogicalClusterDumpsGetter + LogicalClusterMigrationsGetter +} + +// MigrationV1alpha1Client is used to interact with features provided by the migration.kcp.io group. +type MigrationV1alpha1Client struct { + restClient rest.Interface +} + +func (c *MigrationV1alpha1Client) LogicalClusterDumps() LogicalClusterDumpInterface { + return newLogicalClusterDumps(c) +} + +func (c *MigrationV1alpha1Client) LogicalClusterMigrations() LogicalClusterMigrationInterface { + return newLogicalClusterMigrations(c) +} + +// NewForConfig creates a new MigrationV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*MigrationV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new MigrationV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*MigrationV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &MigrationV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new MigrationV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *MigrationV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new MigrationV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *MigrationV1alpha1Client { + return &MigrationV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) { + gv := migrationv1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *MigrationV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go index 2af0156fa8c..d12e41c288a 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go @@ -36,6 +36,7 @@ import ( kcpexternalversionscache "github.com/kcp-dev/sdk/client/informers/externalversions/cache" kcpcore "github.com/kcp-dev/sdk/client/informers/externalversions/core" kcpinternalinterfaces "github.com/kcp-dev/sdk/client/informers/externalversions/internalinterfaces" + kcpmigration "github.com/kcp-dev/sdk/client/informers/externalversions/migration" kcptenancy "github.com/kcp-dev/sdk/client/informers/externalversions/tenancy" kcptopology "github.com/kcp-dev/sdk/client/informers/externalversions/topology" ) @@ -283,6 +284,7 @@ type SharedInformerFactory interface { Apis() kcpapis.ClusterInterface Cache() kcpexternalversionscache.ClusterInterface Core() kcpcore.ClusterInterface + Migration() kcpmigration.ClusterInterface Tenancy() kcptenancy.ClusterInterface Topology() kcptopology.ClusterInterface } @@ -299,6 +301,10 @@ func (f *sharedInformerFactory) Core() kcpcore.ClusterInterface { return kcpcore.New(f, f.tweakListOptions) } +func (f *sharedInformerFactory) Migration() kcpmigration.ClusterInterface { + return kcpmigration.New(f, f.tweakListOptions) +} + func (f *sharedInformerFactory) Tenancy() kcptenancy.ClusterInterface { return kcptenancy.New(f, f.tweakListOptions) } @@ -454,6 +460,7 @@ type SharedScopedInformerFactory interface { Apis() kcpapis.Interface Cache() kcpexternalversionscache.Interface Core() kcpcore.Interface + Migration() kcpmigration.Interface Tenancy() kcptenancy.Interface Topology() kcptopology.Interface } @@ -470,6 +477,10 @@ func (f *sharedScopedInformerFactory) Core() kcpcore.Interface { return kcpcore.NewScoped(f, f.namespace, f.tweakListOptions) } +func (f *sharedScopedInformerFactory) Migration() kcpmigration.Interface { + return kcpmigration.NewScoped(f, f.namespace, f.tweakListOptions) +} + func (f *sharedScopedInformerFactory) Tenancy() kcptenancy.Interface { return kcptenancy.NewScoped(f, f.namespace, f.tweakListOptions) } diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go index 2e5a4f4857b..34c8c3a5a6b 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go @@ -31,6 +31,7 @@ import ( kcpv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" kcpcachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + kcpmigrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" kcptenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" kcptopologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" ) @@ -128,6 +129,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case kcpcorev1alpha1.SchemeGroupVersion.WithResource("shards"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Core().V1alpha1().Shards().Informer()}, nil + // Group=migration.kcp.io, Version=v1alpha1 + case kcpmigrationv1alpha1.SchemeGroupVersion.WithResource("logicalclustermigrations"): + return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Migration().V1alpha1().LogicalClusterMigrations().Informer()}, nil + // Group=tenancy.kcp.io, Version=v1alpha1 case kcptenancyv1alpha1.SchemeGroupVersion.WithResource("workspaces"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Tenancy().V1alpha1().Workspaces().Informer()}, nil @@ -192,6 +197,11 @@ func (f *sharedScopedInformerFactory) ForResource(resource schema.GroupVersionRe informer := f.Core().V1alpha1().Shards().Informer() return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil + // Group=migration.kcp.io, Version=v1alpha1 + case kcpmigrationv1alpha1.SchemeGroupVersion.WithResource("logicalclustermigrations"): + informer := f.Migration().V1alpha1().LogicalClusterMigrations().Informer() + return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil + // Group=tenancy.kcp.io, Version=v1alpha1 case kcptenancyv1alpha1.SchemeGroupVersion.WithResource("workspaces"): informer := f.Tenancy().V1alpha1().Workspaces().Informer() diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/interface.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/interface.go new file mode 100644 index 00000000000..c87884df483 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/interface.go @@ -0,0 +1,67 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-informer-gen. DO NOT EDIT. + +package migration + +import ( + kcpinternalinterfaces "github.com/kcp-dev/sdk/client/informers/externalversions/internalinterfaces" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1" +) + +// ClusterInterface provides access to each of this group's versions. +type ClusterInterface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() kcpv1alpha1.ClusterInterface +} + +type group struct { + factory kcpinternalinterfaces.SharedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// New returns a new ClusterInterface. +func New(f kcpinternalinterfaces.SharedInformerFactory, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) ClusterInterface { + return &group{factory: f, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new kcpv1alpha1.ClusterInterface. +func (g *group) V1alpha1() kcpv1alpha1.ClusterInterface { + return kcpv1alpha1.New(g.factory, g.tweakListOptions) +} + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() kcpv1alpha1.Interface +} + +type scopedGroup struct { + factory kcpinternalinterfaces.SharedScopedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc + namespace string +} + +// New returns a new Interface. +func NewScoped(f kcpinternalinterfaces.SharedScopedInformerFactory, namespace string, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) Interface { + return &scopedGroup{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new kcpv1alpha1.Interface. +func (g *scopedGroup) V1alpha1() kcpv1alpha1.Interface { + return kcpv1alpha1.NewScoped(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/interface.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/interface.go new file mode 100644 index 00000000000..1f0c5faa887 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/interface.go @@ -0,0 +1,64 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + kcpinternalinterfaces "github.com/kcp-dev/sdk/client/informers/externalversions/internalinterfaces" +) + +type ClusterInterface interface { + // LogicalClusterMigrations returns a LogicalClusterMigrationClusterInformer. + LogicalClusterMigrations() LogicalClusterMigrationClusterInformer +} + +type version struct { + factory kcpinternalinterfaces.SharedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f kcpinternalinterfaces.SharedInformerFactory, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) ClusterInterface { + return &version{factory: f, tweakListOptions: tweakListOptions} +} + +// LogicalClusterMigrations returns a LogicalClusterMigrationClusterInformer. +func (v *version) LogicalClusterMigrations() LogicalClusterMigrationClusterInformer { + return &logicalClusterMigrationClusterInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +type Interface interface { + // LogicalClusterMigrations returns a LogicalClusterMigrationInformer. + LogicalClusterMigrations() LogicalClusterMigrationInformer +} + +type scopedVersion struct { + factory kcpinternalinterfaces.SharedScopedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc + namespace string +} + +// New returns a new Interface. +func NewScoped(f kcpinternalinterfaces.SharedScopedInformerFactory, namespace string, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) Interface { + return &scopedVersion{factory: f, tweakListOptions: tweakListOptions} +} + +// LogicalClusterMigrations returns a LogicalClusterMigrationInformer. +func (v *scopedVersion) LogicalClusterMigrations() LogicalClusterMigrationInformer { + return &logicalClusterMigrationScopedInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/logicalclustermigration.go new file mode 100644 index 00000000000..8a4964e955b --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1/logicalclustermigration.go @@ -0,0 +1,182 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + + kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" + kcpinformers "github.com/kcp-dev/apimachinery/v2/third_party/informers" + logicalcluster "github.com/kcp-dev/logicalcluster/v3" + kcpmigrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + kcpversioned "github.com/kcp-dev/sdk/client/clientset/versioned" + kcpcluster "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" + kcpinternalinterfaces "github.com/kcp-dev/sdk/client/informers/externalversions/internalinterfaces" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/listers/migration/v1alpha1" +) + +// LogicalClusterMigrationClusterInformer provides access to a shared informer and lister for +// LogicalClusterMigrations. +type LogicalClusterMigrationClusterInformer interface { + Cluster(logicalcluster.Name) LogicalClusterMigrationInformer + ClusterWithContext(context.Context, logicalcluster.Name) LogicalClusterMigrationInformer + Informer() kcpcache.ScopeableSharedIndexInformer + Lister() kcpv1alpha1.LogicalClusterMigrationClusterLister +} + +type logicalClusterMigrationClusterInformer struct { + factory kcpinternalinterfaces.SharedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// NewLogicalClusterMigrationClusterInformer constructs a new informer for LogicalClusterMigration type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLogicalClusterMigrationClusterInformer(client kcpcluster.ClusterInterface, resyncPeriod time.Duration, indexers cache.Indexers) kcpcache.ScopeableSharedIndexInformer { + return NewFilteredLogicalClusterMigrationClusterInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredLogicalClusterMigrationClusterInformer constructs a new informer for LogicalClusterMigration type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredLogicalClusterMigrationClusterInformer(client kcpcluster.ClusterInterface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) kcpcache.ScopeableSharedIndexInformer { + return kcpinformers.NewSharedIndexInformer( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MigrationV1alpha1().LogicalClusterMigrations().List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MigrationV1alpha1().LogicalClusterMigrations().Watch(context.Background(), options) + }, + }, client), + &kcpmigrationv1alpha1.LogicalClusterMigration{}, + resyncPeriod, + indexers, + ) +} + +func (i *logicalClusterMigrationClusterInformer) defaultInformer(client kcpcluster.ClusterInterface, resyncPeriod time.Duration) kcpcache.ScopeableSharedIndexInformer { + return NewFilteredLogicalClusterMigrationClusterInformer(client, resyncPeriod, cache.Indexers{ + kcpcache.ClusterIndexName: kcpcache.ClusterIndexFunc, + kcpcache.ClusterAndNamespaceIndexName: kcpcache.ClusterAndNamespaceIndexFunc, + }, i.tweakListOptions) +} + +func (i *logicalClusterMigrationClusterInformer) Informer() kcpcache.ScopeableSharedIndexInformer { + return i.factory.InformerFor(&kcpmigrationv1alpha1.LogicalClusterMigration{}, i.defaultInformer) +} + +func (i *logicalClusterMigrationClusterInformer) Lister() kcpv1alpha1.LogicalClusterMigrationClusterLister { + return kcpv1alpha1.NewLogicalClusterMigrationClusterLister(i.Informer().GetIndexer()) +} + +func (i *logicalClusterMigrationClusterInformer) Cluster(clusterName logicalcluster.Name) LogicalClusterMigrationInformer { + return &logicalClusterMigrationInformer{ + informer: i.Informer().Cluster(clusterName), + lister: i.Lister().Cluster(clusterName), + } +} + +func (i *logicalClusterMigrationClusterInformer) ClusterWithContext(ctx context.Context, clusterName logicalcluster.Name) LogicalClusterMigrationInformer { + return &logicalClusterMigrationInformer{ + informer: i.Informer().ClusterWithContext(ctx, clusterName), + lister: i.Lister().Cluster(clusterName), + } +} + +type logicalClusterMigrationInformer struct { + informer cache.SharedIndexInformer + lister kcpv1alpha1.LogicalClusterMigrationLister +} + +func (i *logicalClusterMigrationInformer) Informer() cache.SharedIndexInformer { + return i.informer +} + +func (i *logicalClusterMigrationInformer) Lister() kcpv1alpha1.LogicalClusterMigrationLister { + return i.lister +} + +// LogicalClusterMigrationInformer provides access to a shared informer and lister for +// LogicalClusterMigrations. +type LogicalClusterMigrationInformer interface { + Informer() cache.SharedIndexInformer + Lister() kcpv1alpha1.LogicalClusterMigrationLister +} + +type logicalClusterMigrationScopedInformer struct { + factory kcpinternalinterfaces.SharedScopedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// NewLogicalClusterMigrationInformer constructs a new informer for LogicalClusterMigration type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLogicalClusterMigrationInformer(client kcpversioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredLogicalClusterMigrationInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredLogicalClusterMigrationInformer constructs a new informer for LogicalClusterMigration type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredLogicalClusterMigrationInformer(client kcpversioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MigrationV1alpha1().LogicalClusterMigrations().List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MigrationV1alpha1().LogicalClusterMigrations().Watch(context.Background(), options) + }, + }, client), + &kcpmigrationv1alpha1.LogicalClusterMigration{}, + resyncPeriod, + indexers, + ) +} + +func (i *logicalClusterMigrationScopedInformer) Informer() cache.SharedIndexInformer { + return i.factory.InformerFor(&kcpmigrationv1alpha1.LogicalClusterMigration{}, i.defaultInformer) +} + +func (i *logicalClusterMigrationScopedInformer) Lister() kcpv1alpha1.LogicalClusterMigrationLister { + return kcpv1alpha1.NewLogicalClusterMigrationLister(i.Informer().GetIndexer()) +} + +func (i *logicalClusterMigrationScopedInformer) defaultInformer(client kcpversioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredLogicalClusterMigrationInformer(client, resyncPeriod, cache.Indexers{}, i.tweakListOptions) +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/expansion_generated.go b/staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/expansion_generated.go new file mode 100644 index 00000000000..a4d4e5661f6 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-lister-gen. DO NOT EDIT. + +package v1alpha1 + +// LogicalClusterMigrationClusterListerExpansion allows custom methods to be added to +// LogicalClusterMigrationClusterLister. +type LogicalClusterMigrationClusterListerExpansion interface{} + +// LogicalClusterMigrationListerExpansion allows custom methods to be added to +// LogicalClusterMigrationLister. +type LogicalClusterMigrationListerExpansion interface{} diff --git a/staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/logicalclustermigration.go b/staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/logicalclustermigration.go new file mode 100644 index 00000000000..9f6b18a06a3 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/listers/migration/v1alpha1/logicalclustermigration.go @@ -0,0 +1,102 @@ +/* +Copyright 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. +*/ + +// Code generated by cluster-lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + + kcplisters "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/listers" + "github.com/kcp-dev/logicalcluster/v3" + kcpv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" +) + +// LogicalClusterMigrationClusterLister helps list LogicalClusterMigrations across all workspaces, +// or scope down to a LogicalClusterMigrationLister for one workspace. +// All objects returned here must be treated as read-only. +type LogicalClusterMigrationClusterLister interface { + // List lists all LogicalClusterMigrations in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*kcpv1alpha1.LogicalClusterMigration, err error) + // Cluster returns a lister that can list and get LogicalClusterMigrations in one workspace. + Cluster(clusterName logicalcluster.Name) LogicalClusterMigrationLister + LogicalClusterMigrationClusterListerExpansion +} + +// logicalClusterMigrationClusterLister implements the LogicalClusterMigrationClusterLister interface. +type logicalClusterMigrationClusterLister struct { + kcplisters.ResourceClusterIndexer[*kcpv1alpha1.LogicalClusterMigration] +} + +var _ LogicalClusterMigrationClusterLister = new(logicalClusterMigrationClusterLister) + +// NewLogicalClusterMigrationClusterLister returns a new LogicalClusterMigrationClusterLister. +// We assume that the indexer: +// - is fed by a cross-workspace LIST+WATCH +// - uses kcpcache.MetaClusterNamespaceKeyFunc as the key function +// - has the kcpcache.ClusterIndex as an index +func NewLogicalClusterMigrationClusterLister(indexer cache.Indexer) LogicalClusterMigrationClusterLister { + return &logicalClusterMigrationClusterLister{ + kcplisters.NewCluster[*kcpv1alpha1.LogicalClusterMigration](indexer, kcpv1alpha1.Resource("logicalclustermigration")), + } +} + +// Cluster scopes the lister to one workspace, allowing users to list and get LogicalClusterMigrations. +func (l *logicalClusterMigrationClusterLister) Cluster(clusterName logicalcluster.Name) LogicalClusterMigrationLister { + return &logicalClusterMigrationLister{ + l.ResourceClusterIndexer.WithCluster(clusterName), + } +} + +// logicalClusterMigrationLister can list all LogicalClusterMigrations inside a workspace +// or scope down to a LogicalClusterMigrationNamespaceLister for one namespace. +type logicalClusterMigrationLister struct { + kcplisters.ResourceIndexer[*kcpv1alpha1.LogicalClusterMigration] +} + +var _ LogicalClusterMigrationLister = new(logicalClusterMigrationLister) + +// LogicalClusterMigrationLister can list all LogicalClusterMigrations, or get one in particular. +// All objects returned here must be treated as read-only. +type LogicalClusterMigrationLister interface { + // List lists all LogicalClusterMigrations in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*kcpv1alpha1.LogicalClusterMigration, err error) + // Get retrieves the LogicalClusterMigration from the indexer for a given workspace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*kcpv1alpha1.LogicalClusterMigration, error) + LogicalClusterMigrationListerExpansion +} + +// NewLogicalClusterMigrationLister returns a new LogicalClusterMigrationLister. +// We assume that the indexer: +// - is fed by a cross-workspace LIST+WATCH +// - uses kcpcache.MetaClusterNamespaceKeyFunc as the key function +// - has the kcpcache.ClusterIndex as an index +func NewLogicalClusterMigrationLister(indexer cache.Indexer) LogicalClusterMigrationLister { + return &logicalClusterMigrationLister{ + kcplisters.New[*kcpv1alpha1.LogicalClusterMigration](indexer, kcpv1alpha1.Resource("logicalclustermigration")), + } +} + +// logicalClusterMigrationScopedLister can list all LogicalClusterMigrations inside a workspace +// or scope down to a LogicalClusterMigrationNamespaceLister. +type logicalClusterMigrationScopedLister struct { + kcplisters.ResourceIndexer[*kcpv1alpha1.LogicalClusterMigration] +} diff --git a/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go b/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go index 9b045276780..f4d9f150194 100644 --- a/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go +++ b/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go @@ -32,6 +32,7 @@ import ( v1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" @@ -126,6 +127,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA corev1alpha1.ShardList{}.OpenAPIModelName(): schema_sdk_apis_core_v1alpha1_ShardList(ref), corev1alpha1.ShardSpec{}.OpenAPIModelName(): schema_sdk_apis_core_v1alpha1_ShardSpec(ref), corev1alpha1.ShardStatus{}.OpenAPIModelName(): schema_sdk_apis_core_v1alpha1_ShardStatus(ref), + migrationv1alpha1.EtcdEntry{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_EtcdEntry(ref), + migrationv1alpha1.LogicalClusterDump{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_LogicalClusterDump(ref), + migrationv1alpha1.LogicalClusterDumpSpec{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_LogicalClusterDumpSpec(ref), + migrationv1alpha1.LogicalClusterDumpStatus{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_LogicalClusterDumpStatus(ref), + migrationv1alpha1.LogicalClusterMigration{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_LogicalClusterMigration(ref), + migrationv1alpha1.LogicalClusterMigrationList{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_LogicalClusterMigrationList(ref), + migrationv1alpha1.LogicalClusterMigrationSpec{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_LogicalClusterMigrationSpec(ref), + migrationv1alpha1.LogicalClusterMigrationStatus{}.OpenAPIModelName(): schema_sdk_apis_migration_v1alpha1_LogicalClusterMigrationStatus(ref), tenancyv1alpha1.APIExportReference{}.OpenAPIModelName(): schema_sdk_apis_tenancy_v1alpha1_APIExportReference(ref), tenancyv1alpha1.AuthenticationConfigurationReference{}.OpenAPIModelName(): schema_sdk_apis_tenancy_v1alpha1_AuthenticationConfigurationReference(ref), tenancyv1alpha1.ClaimMappings{}.OpenAPIModelName(): schema_sdk_apis_tenancy_v1alpha1_ClaimMappings(ref), @@ -4089,6 +4098,291 @@ func schema_sdk_apis_core_v1alpha1_ShardStatus(ref common.ReferenceCallback) com } } +func schema_sdk_apis_migration_v1alpha1_EtcdEntry(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EtcdEntry is a single etcd key/value pair from the origin shard.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the etcd key with the origin shard's storage prefix stripped. The destination shard prepends its own storage prefix before writing.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the raw etcd value bytes. JSON-encoded as base64 on the wire.", + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + Required: []string{"key", "value"}, + }, + }, + } +} + +func schema_sdk_apis_migration_v1alpha1_LogicalClusterDump(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LogicalClusterDump is an ephemeral request/response type used by the destination shard during a logical cluster migration to fetch the raw etcd contents of the migration logical cluster from the origin shard.\n\nThe server populates Status.Entries on Create. The object is not persisted.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(migrationv1alpha1.LogicalClusterDumpSpec{}.OpenAPIModelName()), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(migrationv1alpha1.LogicalClusterDumpStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + migrationv1alpha1.LogicalClusterDumpSpec{}.OpenAPIModelName(), migrationv1alpha1.LogicalClusterDumpStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_sdk_apis_migration_v1alpha1_LogicalClusterDumpSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LogicalClusterDumpSpec is the desired state for a dump request.\n\nThe logical cluster to dump is taken from the request's cluster context (i.e. the URL the request arrived on). No fields are required today.", + Type: []string{"object"}, + }, + }, + } +} + +func schema_sdk_apis_migration_v1alpha1_LogicalClusterDumpStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LogicalClusterDumpStatus carries the dump payload populated by the server.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "entries": { + SchemaProps: spec.SchemaProps{ + Description: "entries is the full set of etcd key/value pairs belonging to the logical cluster, in the origin shard's etcd encoding (proto for built-ins, JSON for CRs).\n\nbecomes a concern.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(migrationv1alpha1.EtcdEntry{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + migrationv1alpha1.EtcdEntry{}.OpenAPIModelName()}, + } +} + +func schema_sdk_apis_migration_v1alpha1_LogicalClusterMigration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LogicalClusterMigration describes a migration of a logical cluster from one shard to another.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(migrationv1alpha1.LogicalClusterMigrationSpec{}.OpenAPIModelName()), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(migrationv1alpha1.LogicalClusterMigrationStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + migrationv1alpha1.LogicalClusterMigrationSpec{}.OpenAPIModelName(), migrationv1alpha1.LogicalClusterMigrationStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_sdk_apis_migration_v1alpha1_LogicalClusterMigrationList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LogicalClusterMigrationList is a list of LogicalClusterMigration resources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(migrationv1alpha1.LogicalClusterMigration{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + migrationv1alpha1.LogicalClusterMigration{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, + } +} + +func schema_sdk_apis_migration_v1alpha1_LogicalClusterMigrationSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LogicalClusterMigrationSpec holds the desired state of the migration.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "logicalCluster": { + SchemaProps: spec.SchemaProps{ + Description: "logicalCluster is the name of the logical cluster to migrate.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "destinationShard": { + SchemaProps: spec.SchemaProps{ + Description: "destinationShard is the name of the shard to migrate the logical cluster to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"logicalCluster", "destinationShard"}, + }, + }, + } +} + +func schema_sdk_apis_migration_v1alpha1_LogicalClusterMigrationStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LogicalClusterMigrationStatus communicates the observed state of the migration.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "phase is the current phase of the migration.", + Type: []string{"string"}, + Format: "", + }, + }, + "originShard": { + SchemaProps: spec.SchemaProps{ + Description: "originShard is the name of the shard to migrate the logical cluster from. Set by the origin shard controller during preparation.", + Type: []string{"string"}, + Format: "", + }, + }, + "conditions": { + SchemaProps: spec.SchemaProps{ + Description: "Current processing state of the migration.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(conditionsv1alpha1.Condition{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + conditionsv1alpha1.Condition{}.OpenAPIModelName()}, + } +} + func schema_sdk_apis_tenancy_v1alpha1_APIExportReference(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ From 46c52004410048175297de24b2a4dd457ba44197 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Thu, 4 Jun 2026 09:37:20 +0200 Subject: [PATCH 08/26] Bootstrap LogicalClusterMigration Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- config/system-crds/bootstrap.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/system-crds/bootstrap.go b/config/system-crds/bootstrap.go index 2888be7094c..42bb7f974e6 100644 --- a/config/system-crds/bootstrap.go +++ b/config/system-crds/bootstrap.go @@ -33,6 +33,7 @@ import ( "github.com/kcp-dev/sdk/apis/apis" "github.com/kcp-dev/sdk/apis/cache" "github.com/kcp-dev/sdk/apis/core" + "github.com/kcp-dev/sdk/apis/migration" configcrds "github.com/kcp-dev/kcp/config/crds" confighelpers "github.com/kcp-dev/kcp/config/helpers" @@ -57,6 +58,7 @@ func Bootstrap(ctx context.Context, crdClient apiextensionsclient.Interface, dis {Group: apis.GroupName, Resource: "apiresourceschemas"}, {Group: apis.GroupName, Resource: "apiexportendpointslices"}, {Group: core.GroupName, Resource: "logicalclusters"}, + {Group: migration.GroupName, Resource: "logicalclustermigrations"}, {Group: apis.GroupName, Resource: "apiconversions"}, {Group: cache.GroupName, Resource: "cachedresources"}, {Group: cache.GroupName, Resource: "cachedresourceendpointslices"}, From 3a95fa6e062aa0786f62a9681e1419c11ce2dd54 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Thu, 4 Jun 2026 09:34:30 +0200 Subject: [PATCH 09/26] Replicate LogicalClusterMigration to cache server Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/cache/server/bootstrap/bootstrap.go | 1 + pkg/reconciler/cache/replication/replication_controller.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/pkg/cache/server/bootstrap/bootstrap.go b/pkg/cache/server/bootstrap/bootstrap.go index daa50901aef..93ab2e60048 100644 --- a/pkg/cache/server/bootstrap/bootstrap.go +++ b/pkg/cache/server/bootstrap/bootstrap.go @@ -46,6 +46,7 @@ func Bootstrap(ctx context.Context, apiExtensionsClusterClient kcpapiextensionsc {"apis.kcp.io", "apiexports"}, {"apis.kcp.io", "apiexportendpointslices"}, {"core.kcp.io", "logicalclusters"}, + {"migration.kcp.io", "logicalclustermigrations"}, {"core.kcp.io", "shards"}, {"cache.kcp.io", "cachedobjects"}, {"cache.kcp.io", "cachedresources"}, diff --git a/pkg/reconciler/cache/replication/replication_controller.go b/pkg/reconciler/cache/replication/replication_controller.go index ac6e35ce0ad..9349c538217 100644 --- a/pkg/reconciler/cache/replication/replication_controller.go +++ b/pkg/reconciler/cache/replication/replication_controller.go @@ -40,6 +40,7 @@ import ( cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" "github.com/kcp-dev/sdk/apis/core" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migration "github.com/kcp-dev/sdk/apis/migration/v1alpha1" tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" kcpinformers "github.com/kcp-dev/sdk/client/informers/externalversions" @@ -275,6 +276,11 @@ func InstallIndexers( Local: localKcpInformers.Core().V1alpha1().LogicalClusters().Informer(), Global: globalKcpInformers.Core().V1alpha1().LogicalClusters().Informer(), }, + migration.SchemeGroupVersion.WithResource("logicalclustermigrations"): { + Kind: "LogicalClusterMigration", + Local: localKcpInformers.Migration().V1alpha1().LogicalClusterMigrations().Informer(), + Global: globalKcpInformers.Migration().V1alpha1().LogicalClusterMigrations().Informer(), + }, tenancyv1alpha1.SchemeGroupVersion.WithResource("workspacetypes"): { Kind: "WorkspaceType", Local: localKcpInformers.Tenancy().V1alpha1().WorkspaceTypes().Informer(), From 4e8b5bf1fde3708aaef6df45a700cc52c330dff0 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Thu, 4 Jun 2026 10:43:03 +0200 Subject: [PATCH 10/26] Add LogicalClusterMigration feature flag Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/features/kcp_features.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/features/kcp_features.go b/pkg/features/kcp_features.go index 161b0098798..51c5ef53d11 100644 --- a/pkg/features/kcp_features.go +++ b/pkg/features/kcp_features.go @@ -61,6 +61,11 @@ const ( // users into workspaces from foreign OIDC issuers. This feature can be individually enabled on each shard and // the front-proxy. WorkspaceAuthentication featuregate.Feature = "WorkspaceAuthentication" + + // owner: @ntnn + // alpha: v0.1 + // Enables migrating logical clusters between shards. + LogicalClusterMigration featuregate.Feature = "LogicalClusterMigration" ) // DefaultFeatureGate exposes the upstream feature gate, but with our gate setting applied. @@ -142,6 +147,9 @@ var defaultVersionedGenericControlPlaneFeatureGates = map[featuregate.Feature]fe WorkspaceAuthentication: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, }, + LogicalClusterMigration: { + {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, + }, // inherited features from generic apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: genericfeatures.APIResponseCompression: { From b54b070c9b59560a5c662923a6fe56b94b2c8f3b Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Thu, 4 Jun 2026 10:43:42 +0200 Subject: [PATCH 11/26] Enable LogicalClusterMigration feature in tests Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- Makefile | 2 +- test/e2e/framework/kcp.go | 2 +- test/integration/framework/server.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f1927febb67..30eec0582ed 100644 --- a/Makefile +++ b/Makefile @@ -321,7 +321,7 @@ ifdef SUITES SUITES_ARG = --suites $(SUITES) COMPLETE_SUITES_ARG = -args $(SUITES_ARG) endif -TEST_FEATURE_GATES ?= WorkspaceMounts=true,CacheAPIs=true,WorkspaceAuthentication=true +TEST_FEATURE_GATES ?= WorkspaceMounts=true,CacheAPIs=true,WorkspaceAuthentication=true,LogicalClusterMigration=true PROXY_FEATURE_GATES ?= $(TEST_FEATURE_GATES) .PHONY: test-e2e diff --git a/test/e2e/framework/kcp.go b/test/e2e/framework/kcp.go index 2a525594770..3ea00ed1aa6 100644 --- a/test/e2e/framework/kcp.go +++ b/test/e2e/framework/kcp.go @@ -37,6 +37,6 @@ func init() { kcptesting.InitSharedKcpServer(kcptestingserver.WithCustomArguments( "--token-auth-file", DefaultTokenAuthFile, - "--feature-gates=WorkspaceMounts=true,CacheAPIs=true,WorkspaceAuthentication=true", + "--feature-gates=WorkspaceMounts=true,CacheAPIs=true,WorkspaceAuthentication=true,LogicalClusterMigration=true", )) } diff --git a/test/integration/framework/server.go b/test/integration/framework/server.go index bc79760a14d..1478cc418f5 100644 --- a/test/integration/framework/server.go +++ b/test/integration/framework/server.go @@ -81,7 +81,7 @@ func NewInProcessServer(t kcptestingserver.TestingT, opts ...kcptestingserver.Op kcptestingserver.WithDefaultsFrom(t), kcptestingserver.WithRunInProcess(), kcptestingserver.WithBindAddress("127.0.0.1"), - kcptestingserver.WithCustomArguments("--feature-gates=WorkspaceMounts=true,CacheAPIs=true,WorkspaceAuthentication=true"), + kcptestingserver.WithCustomArguments("--feature-gates=WorkspaceMounts=true,CacheAPIs=true,WorkspaceAuthentication=true,LogicalClusterMigration=true"), }, opts..., ) From 4a8f3b0fcad4fbc0f4e56d0be93043742571e76b Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:28:11 +0200 Subject: [PATCH 12/26] Add MigratingLogicalClusters Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../migratinglogicalclusters.go | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 pkg/reconciler/migrating/logicalclustermigration/migratinglogicalclusters.go diff --git a/pkg/reconciler/migrating/logicalclustermigration/migratinglogicalclusters.go b/pkg/reconciler/migrating/logicalclustermigration/migratinglogicalclusters.go new file mode 100644 index 00000000000..0ad06aa5c4e --- /dev/null +++ b/pkg/reconciler/migrating/logicalclustermigration/migratinglogicalclusters.go @@ -0,0 +1,64 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "fmt" + "sync" + + "github.com/kcp-dev/logicalcluster/v3" +) + +// MigratingLogicalClusters tracks which logical clusters are in migration. +type MigratingLogicalClusters struct { + clusters sync.Map // logicalcluster.Name → string (migration object path) +} + +func NewMigratingLogicalClusters() *MigratingLogicalClusters { + return &MigratingLogicalClusters{} +} + +// Set marks a logical cluster as migrating. +// The migrationPath is the path to the LogicalClusterMigration that triggered +// the migration in the form :. +func (m *MigratingLogicalClusters) Set(name logicalcluster.Name, migrationPath string) error { + cur, loaded := m.clusters.LoadOrStore(name, migrationPath) + if loaded && cur != migrationPath { + return fmt.Errorf("tried to register logical cluster %s as migrating that was already in migration from a different path: existing=%s new=%s", name, cur, migrationPath) + } + return nil +} + +// Remove marks a logical cluster as no longer migrating. +func (m *MigratingLogicalClusters) Remove(name logicalcluster.Name) { + m.clusters.Delete(name) +} + +// Get returns the migration object path for a migrating logical cluster. +func (m *MigratingLogicalClusters) Get(name logicalcluster.Name) (string, bool) { + v, ok := m.clusters.Load(name) + if !ok { + return "", false + } + return v.(string), true +} + +// IsMigrating returns whether the logical cluster is currently being migrated. +func (m *MigratingLogicalClusters) IsMigrating(name logicalcluster.Name) bool { + _, ok := m.clusters.Load(name) + return ok +} From f9b3aaa56327208a9f61373835413a34ff488896 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:30:23 +0200 Subject: [PATCH 13/26] Wire MigratingLogicalClusters into server Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/server/config.go | 3 +++ pkg/server/server.go | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/server/config.go b/pkg/server/config.go index 740fcf18b4c..2d4ba264154 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -74,6 +74,7 @@ import ( "github.com/kcp-dev/kcp/pkg/informer" "github.com/kcp-dev/kcp/pkg/network" "github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper" + "github.com/kcp-dev/kcp/pkg/reconciler/migrating/logicalclustermigration" "github.com/kcp-dev/kcp/pkg/server/aggregatingcrdversiondiscovery" "github.com/kcp-dev/kcp/pkg/server/bootstrap" kcpfilters "github.com/kcp-dev/kcp/pkg/server/filters" @@ -134,6 +135,7 @@ type ExtraConfig struct { preHandlerChainMux *handlerChainMuxes quotaAdmissionStopCh chan struct{} ClusterContextManager *contextmanager.Manager[logicalcluster.Path] + MigratingLogicalClusters *logicalclustermigration.MigratingLogicalClusters openAPIv3Controller *openapiv3.Controller openAPIv3ServiceCache *openapiv3.ServiceCache @@ -633,6 +635,7 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co quotaConfiguration := generic.NewConfiguration(nil, quotainstall.DefaultIgnoredResources()) c.ExtraConfig.quotaAdmissionStopCh = make(chan struct{}) + c.ExtraConfig.MigratingLogicalClusters = logicalclustermigration.NewMigratingLogicalClusters() // DynamicRESTMapper is initialized here, but it starts to be populated only once its controller starts. c.DynamicRESTMapper = dynamicrestmapper.NewDynamicRESTMapper() diff --git a/pkg/server/server.go b/pkg/server/server.go index 157501b9fdc..16b58c5123d 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -26,6 +26,7 @@ import ( extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" @@ -176,10 +177,22 @@ func NewServer(c CompletedConfig) (*Server, error) { return nil, err } + isFromMigratingLogicalCluster := func(obj any) bool { + if d, ok := obj.(cache.DeletedFinalStateUnknown); ok { + obj = d.Obj + } + accessor, err := meta.Accessor(obj) + if err != nil { + return false + } + clusterName := logicalcluster.From(accessor) + return c.MigratingLogicalClusters.IsMigrating(clusterName) + } + s.PartialMetadataDDSIF, err = informer.NewDiscoveringDynamicSharedInformerFactory( metadataClusterClient, func(obj any) bool { return true }, - func(obj any) bool { return false }, + isFromMigratingLogicalCluster, nil, crdGVRSource, cache.Indexers{}, @@ -203,7 +216,7 @@ func NewServer(c CompletedConfig) (*Server, error) { s.CachePartialMetadataDDSIF, err = informer.NewDiscoveringDynamicSharedInformerFactory( cacheMetadataClusterClient, func(obj interface{}) bool { return true }, - func(obj any) bool { return false }, + isFromMigratingLogicalCluster, nil, cacheCrdGVRSource, cache.Indexers{}, From d4364a626049404fe59784722c994d809f91e54c Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:31:48 +0200 Subject: [PATCH 14/26] Add logicalclustermigration.Controller Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../logicalclustermigration/controller.go | 240 +++++++++++++++ .../logicalclustermigration/datacleanup.go | 136 +++++++++ .../logicalclustermigration/datacopy.go | 83 ++++++ .../migrating/logicalclustermigration/doc.go | 49 ++++ .../logicalclustermigration/reconciler.go | 277 ++++++++++++++++++ 5 files changed, 785 insertions(+) create mode 100644 pkg/reconciler/migrating/logicalclustermigration/controller.go create mode 100644 pkg/reconciler/migrating/logicalclustermigration/datacleanup.go create mode 100644 pkg/reconciler/migrating/logicalclustermigration/datacopy.go create mode 100644 pkg/reconciler/migrating/logicalclustermigration/doc.go create mode 100644 pkg/reconciler/migrating/logicalclustermigration/reconciler.go diff --git a/pkg/reconciler/migrating/logicalclustermigration/controller.go b/pkg/reconciler/migrating/logicalclustermigration/controller.go new file mode 100644 index 00000000000..60b2b130662 --- /dev/null +++ b/pkg/reconciler/migrating/logicalclustermigration/controller.go @@ -0,0 +1,240 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "context" + "fmt" + "time" + + clientv3 "go.etcd.io/etcd/client/v3" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + + kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" + "github.com/kcp-dev/logicalcluster/v3" + corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + kcpclientset "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" + migrationv1alpha1client "github.com/kcp-dev/sdk/client/clientset/versioned/typed/migration/v1alpha1" + corev1alpha1informers "github.com/kcp-dev/sdk/client/informers/externalversions/core/v1alpha1" + migrationv1alpha1informers "github.com/kcp-dev/sdk/client/informers/externalversions/migration/v1alpha1" + corev1alpha1listers "github.com/kcp-dev/sdk/client/listers/core/v1alpha1" + + "github.com/kcp-dev/kcp/pkg/informer" + "github.com/kcp-dev/kcp/pkg/logging" + "github.com/kcp-dev/kcp/pkg/reconciler/committer" +) + +const ( + ControllerName = "kcp-logicalcluster-migration" + + // MigratingAnnotationKey is the annotation set on the LogicalCluster object to + // indicate it is currently being migrated. The value is the cluster path + // of the LogicalClusterMigration object that triggered the migration. + MigratingAnnotationKey = "internal.kcp.io/migrating" +) + +func NewController( + shardName string, + kcpClusterClient kcpclientset.ClusterInterface, + externalLogicalClusterAdminConfig *rest.Config, + etcdClient *clientv3.Client, + etcdStoragePrefix string, + cachedLogicalClusterMigrationInformer migrationv1alpha1informers.LogicalClusterMigrationClusterInformer, + localLogicalClusterInformer corev1alpha1informers.LogicalClusterClusterInformer, + cachedShardInformer corev1alpha1informers.ShardClusterInformer, + migratingLogicalClusters *MigratingLogicalClusters, + cancelLogicalClusterConnections func(logicalcluster.Path, error), + ddsif *informer.DiscoveringDynamicSharedInformerFactory, +) (*Controller, error) { + c := &Controller{ + queue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.DefaultTypedControllerRateLimiter[string](), + workqueue.TypedRateLimitingQueueConfig[string]{ + Name: ControllerName, + }, + ), + shardName: shardName, + kcpClusterClient: kcpClusterClient, + externalLogicalClusterAdminConfig: externalLogicalClusterAdminConfig, + etcdClient: etcdClient, + etcdStoragePrefix: etcdStoragePrefix, + logicalClusterLister: localLogicalClusterInformer.Lister(), + shardLister: cachedShardInformer.Lister(), + migratingLogicalClusters: migratingLogicalClusters, + cancelLogicalClusterConnections: cancelLogicalClusterConnections, + ddsif: ddsif, + commit: committer.NewCommitter[*migrationv1alpha1.LogicalClusterMigration, migrationv1alpha1client.LogicalClusterMigrationInterface, *migrationv1alpha1.LogicalClusterMigrationSpec, *migrationv1alpha1.LogicalClusterMigrationStatus](kcpClusterClient.MigrationV1alpha1().LogicalClusterMigrations()), + } + + // Events are queued from the cache server as that is used to + // coordinate the migration between the participating shards. + // The updates are written through the front-proxy to the + // LogicalClusterMigration, the owning shard then replicates these + // changes to the cache server which then triggers the next phase in + // the respective shard. + _, _ = cachedLogicalClusterMigrationInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { c.enqueue(obj) }, + UpdateFunc: func(_, obj interface{}) { c.enqueue(obj) }, + DeleteFunc: func(obj interface{}) { c.enqueue(obj) }, + }) + + return c, nil +} + +type logicalClusterMigrationResource = committer.Resource[*migrationv1alpha1.LogicalClusterMigrationSpec, *migrationv1alpha1.LogicalClusterMigrationStatus] + +type Controller struct { + queue workqueue.TypedRateLimitingInterface[string] + + shardName string + + kcpClusterClient kcpclientset.ClusterInterface + externalLogicalClusterAdminConfig *rest.Config + etcdClient *clientv3.Client + etcdStoragePrefix string + + logicalClusterLister corev1alpha1listers.LogicalClusterClusterLister + shardLister corev1alpha1listers.ShardClusterLister + + migratingLogicalClusters *MigratingLogicalClusters + cancelLogicalClusterConnections func(logicalcluster.Path, error) + ddsif *informer.DiscoveringDynamicSharedInformerFactory + + commit func(ctx context.Context, old, new *logicalClusterMigrationResource) error +} + +func (c *Controller) enqueue(obj interface{}) { + key, err := kcpcache.DeletionHandlingMetaClusterNamespaceKeyFunc(obj) + if err != nil { + utilruntime.HandleError(err) + return + } + logger := logging.WithQueueKey(logging.WithReconciler(klog.Background(), ControllerName), key) + logger.V(4).Info("queueing LogicalClusterMigration") + c.queue.Add(key) +} + +func (c *Controller) Start(ctx context.Context, numThreads int) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + logger := logging.WithReconciler(klog.FromContext(ctx), ControllerName) + ctx = klog.NewContext(ctx, logger) + logger.Info("Starting controller") + defer logger.Info("Shutting down controller") + + for range numThreads { + go wait.Until(func() { c.startWorker(ctx) }, time.Second, ctx.Done()) + } + + <-ctx.Done() +} + +func (c *Controller) startWorker(ctx context.Context) { + for c.processNextWorkItem(ctx) { + } +} + +func (c *Controller) processNextWorkItem(ctx context.Context) bool { + k, quit := c.queue.Get() + if quit { + return false + } + key := k + + logger := logging.WithQueueKey(klog.FromContext(ctx), key) + ctx = klog.NewContext(ctx, logger) + logger.V(4).Info("processing key") + + defer c.queue.Done(key) + + if requeue, err := c.process(ctx, key); err != nil { + utilruntime.HandleError(fmt.Errorf("%q controller failed to sync %q, err: %w", ControllerName, key, err)) + c.queue.AddRateLimited(key) + return true + } else if requeue { + c.queue.Add(key) + return true + } + c.queue.Forget(key) + return true +} + +func (c *Controller) process(ctx context.Context, key string) (bool, error) { + logger := klog.FromContext(ctx) + + clusterName, _, name, err := kcpcache.SplitMetaClusterNamespaceKey(key) + if err != nil { + logger.Error(err, "unable to decode key") + return false, nil + } + + migration, err := c.kcpClusterClient.Cluster(clusterName.Path()).MigrationV1alpha1().LogicalClusterMigrations().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + + old := migration + migration = migration.DeepCopy() + + logger = logging.WithObject(logger, migration) + ctx = klog.NewContext(ctx, logger) + + var errs []error + requeue, err := c.reconcile(ctx, migration) + if err != nil { + errs = append(errs, err) + } + + oldResource := &logicalClusterMigrationResource{ObjectMeta: old.ObjectMeta, Spec: &old.Spec, Status: &old.Status} + newResource := &logicalClusterMigrationResource{ObjectMeta: migration.ObjectMeta, Spec: &migration.Spec, Status: &migration.Status} + if err := c.commit(ctx, oldResource, newResource); err != nil { + errs = append(errs, err) + } + + return requeue, utilerrors.NewAggregate(errs) +} + +// isOrigin returns true if this shard is the origin of the migration. +// Once the origin shard has set status.originShard, we use that as the source of truth. +// On first reconcile (before status is set), we check whether the LC exists locally. +func (c *Controller) isOrigin(migration *migrationv1alpha1.LogicalClusterMigration) bool { + if migration.Status.OriginShard != "" { + return migration.Status.OriginShard == c.shardName + } + lcName := logicalcluster.Name(migration.Spec.LogicalCluster) + _, err := c.logicalClusterLister.Cluster(lcName).Get(corev1alpha1.LogicalClusterName) + return err == nil +} + +// isDestination returns true if this shard is the intended destination. +func (c *Controller) isDestination(migration *migrationv1alpha1.LogicalClusterMigration) bool { + return migration.Spec.DestinationShard == c.shardName +} diff --git a/pkg/reconciler/migrating/logicalclustermigration/datacleanup.go b/pkg/reconciler/migrating/logicalclustermigration/datacleanup.go new file mode 100644 index 00000000000..d716880fae2 --- /dev/null +++ b/pkg/reconciler/migrating/logicalclustermigration/datacleanup.go @@ -0,0 +1,136 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "context" + "fmt" + "strings" + + clientv3 "go.etcd.io/etcd/client/v3" + + "k8s.io/klog/v2" + + "github.com/kcp-dev/logicalcluster/v3" +) + +const etcdPageSize int64 = 1000 + +// deleteOriginData removes all etcd data belonging to the given logical cluster. +func (c *Controller) deleteOriginData(ctx context.Context, lcName logicalcluster.Name) error { + logger := klog.FromContext(ctx) + logger.V(2).Info("cleaning up origin data via etcd", "logicalCluster", lcName) + + prefix := c.etcdStoragePrefix + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + // TODO(ntnn): This isn't very solid and just assumes the happy + // path. The deletion should probably stop and requeue on any error. + + prefixes := make(chan string) + errCh := make(chan error, 1) + + go func() { + defer close(errCh) + errCh <- c.scanEtcdKeys(ctx, prefix, lcName, prefixes) + }() + + var deleteErrors []error + for p := range prefixes { + logger.V(4).Info("deleting etcd prefix", "prefix", p) + if _, err := c.etcdClient.Delete(ctx, p, clientv3.WithPrefix()); err != nil { + deleteErrors = append(deleteErrors, fmt.Errorf("failed to delete prefix %s: %w", p, err)) + } + } + + if err := <-errCh; err != nil { + deleteErrors = append(deleteErrors, err) + } + + if len(deleteErrors) > 0 { + return fmt.Errorf("origin cleanup encountered %d errors: %v", len(deleteErrors), deleteErrors) + } + + return nil +} + +func (c *Controller) scanEtcdKeys(ctx context.Context, prefix string, targetLogicalCluster logicalcluster.Name, out chan<- string) error { + defer close(out) + + seen := make(map[string]struct{}) + + key := prefix + for { + resp, err := c.etcdClient.Get(ctx, key, + clientv3.WithRange(clientv3.GetPrefixRangeEnd(prefix)), + clientv3.WithKeysOnly(), + clientv3.WithLimit(etcdPageSize), + ) + if err != nil { + return fmt.Errorf("failed to list etcd keys: %w", err) + } + + for _, kv := range resp.Kvs { + if err := ctx.Err(); err != nil { + return err + } + + etcdKeyPrefix, logicalClusterName := parseKey(prefix, string(kv.Key)) + if logicalClusterName != targetLogicalCluster+"/" { + continue + } + if _, exists := seen[etcdKeyPrefix]; exists { + continue + } + seen[etcdKeyPrefix] = struct{}{} + + out <- etcdKeyPrefix + } + + if !resp.More { + return nil + } + key = string(resp.Kvs[len(resp.Kvs)-1].Key) + "\x00" + } +} + +// parseKey parses an etcd key and returns the deletion prefix that scopes +// to a single logical cluster, plus the logical cluster name itself. +func parseKey(prefix, key string) (string, logicalcluster.Name) { + // /prefix/group/resource/[customresources/]lc/something + // -> [/]group/resource/[customresources/]lc/something + key = strings.TrimPrefix(key, prefix) + // -> group/resource/[customresources/]lc/something + key = strings.TrimPrefix(key, "/") + // -> group, resource, [customresources,] lc, something... + parts := strings.SplitN(key, "/", 5) + if len(parts) < 3 { + // Too short to be a resource key + return "", "" + } + // ///customresources//... + if parts[2] == "customresources" { + if len(parts) < 4 { + return "", "" + } + return prefix + parts[0] + "/" + parts[1] + "/customresources/" + parts[3], logicalcluster.Name(parts[3]) + "/" + } + // ////... + return prefix + parts[0] + "/" + parts[1] + "/" + parts[2], logicalcluster.Name(parts[2]) + "/" +} diff --git a/pkg/reconciler/migrating/logicalclustermigration/datacopy.go b/pkg/reconciler/migrating/logicalclustermigration/datacopy.go new file mode 100644 index 00000000000..26956521f0b --- /dev/null +++ b/pkg/reconciler/migrating/logicalclustermigration/datacopy.go @@ -0,0 +1,83 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "context" + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/klog/v2" + + "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/sdk/apis/core" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + kcpsdkclient "github.com/kcp-dev/sdk/client/clientset/versioned" + + "github.com/kcp-dev/kcp/pkg/virtual/migratingworkspaces" +) + +// copyDataFromOrigin requests a LogicalClusterDump from the origin shard's +// migrating virtual workspace and writes the returned etcd entries directly +// to the local shard's etcd. +func (c *Controller) copyDataFromOrigin(ctx context.Context, lcName logicalcluster.Name, originShardName string) error { + logger := klog.FromContext(ctx) + + originShard, err := c.shardLister.Cluster(core.RootCluster).Get(originShardName) + if err != nil { + return fmt.Errorf("failed to get origin shard %q: %w", originShardName, err) + } + if originShard.Spec.VirtualWorkspaceURL == "" { + return fmt.Errorf("origin shard %q has no VirtualWorkspaceURL", originShardName) + } + + cfg := rest.CopyConfig(c.externalLogicalClusterAdminConfig) + cfg.Host = originShard.Spec.VirtualWorkspaceURL + migratingworkspaces.URLFor() + "/clusters/" + string(lcName) + + client, err := kcpsdkclient.NewForConfig(cfg) + if err != nil { + return fmt.Errorf("failed to create origin migrating client: %w", err) + } + + logger.V(2).Info("requesting logical cluster dump from origin", "logicalCluster", lcName, "originShard", originShardName) + + dump, err := client.MigrationV1alpha1().LogicalClusterDumps().Create(ctx, &migrationv1alpha1.LogicalClusterDump{}, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to dump logical cluster from origin: %w", err) + } + + destPrefix := c.etcdStoragePrefix + if !strings.HasSuffix(destPrefix, "/") { + destPrefix += "/" + } + + logger.V(2).Info("writing dump entries to local etcd", "logicalCluster", lcName, "entries", len(dump.Status.Entries)) + + for _, entry := range dump.Status.Entries { + if err := ctx.Err(); err != nil { + return err + } + key := destPrefix + strings.TrimPrefix(entry.Key, "/") + if _, err := c.etcdClient.Put(ctx, key, string(entry.Value)); err != nil { + return fmt.Errorf("failed to write etcd key %q: %w", key, err) + } + } + + return nil +} diff --git a/pkg/reconciler/migrating/logicalclustermigration/doc.go b/pkg/reconciler/migrating/logicalclustermigration/doc.go new file mode 100644 index 00000000000..9cb3687be4c --- /dev/null +++ b/pkg/reconciler/migrating/logicalclustermigration/doc.go @@ -0,0 +1,49 @@ +/* +Copyright 2026 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 logicalclustermigration implements migrating logical clusters between shards. +// +// The migration process is roughly: +// +// - LogicalClusterMigration is created +// +// Origin shard (reconcilePreparing): +// +// - Sets itself in the .status.originShard +// - Annotates the LC to prevent access +// - Configures informers to ignore objects related to the LC +// - Cancels all active connections to the LC +// - Purges all objects in informers related to the LC +// +// Destination shard (reconcileMigrating): +// +// - Configures informers to ignore objects related to the LC +// - Copies over all objects from the origin shard and writes them +// directly to the etcd +// +// Origin shard (reconcileOriginCleanup): +// +// - Purges all objects, including the LC from etcd +// +// Destination shard (reconcileDestinationFinalize): +// +// - Updates the LC to remove the annotation +// - Resyncs all informers so they pick up the LC's objects +// +// The front-proxy automatically bars access to the LC and routes +// requests to the new shard by discovering the objects on the shards +// directly. +package logicalclustermigration diff --git a/pkg/reconciler/migrating/logicalclustermigration/reconciler.go b/pkg/reconciler/migrating/logicalclustermigration/reconciler.go new file mode 100644 index 00000000000..1f2b6c2670a --- /dev/null +++ b/pkg/reconciler/migrating/logicalclustermigration/reconciler.go @@ -0,0 +1,277 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "context" + "errors" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" + + "github.com/kcp-dev/logicalcluster/v3" + corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" + "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" +) + +func (c *Controller) reconcile(ctx context.Context, migration *migrationv1alpha1.LogicalClusterMigration) (bool, error) { + logger := klog.FromContext(ctx) + + isOrigin := c.isOrigin(migration) + isDestination := c.isDestination(migration) + + if !isOrigin && !isDestination { + logger.V(4).Info("migration is not for this shard, skipping") + return false, nil + } + + // Route through the process. The overview of the process id + // described in the doc.go, details are in the methods. + switch migration.Status.Phase { + case migrationv1alpha1.LogicalClusterMigrationPhaseCompleted, migrationv1alpha1.LogicalClusterMigrationPhaseFailed: + return false, nil + + case "": + if isOrigin { + migration.Status.Phase = migrationv1alpha1.LogicalClusterMigrationPhasePreparing + return true, nil + } + return false, nil + + case migrationv1alpha1.LogicalClusterMigrationPhasePreparing: + if isOrigin { + return c.reconcilePreparing(ctx, migration) + } + return false, nil + + case migrationv1alpha1.LogicalClusterMigrationPhaseMigrating: + if isDestination { + return c.reconcileMigrating(ctx, migration) + } + return false, nil + + case migrationv1alpha1.LogicalClusterMigrationPhaseOriginCleanup: + if isOrigin { + return c.reconcileOriginCleanup(ctx, migration) + } + return false, nil + + case migrationv1alpha1.LogicalClusterMigrationPhaseDestinationFinalize: + if isDestination { + return c.reconcileDestinationFinalize(ctx, migration) + } + return false, nil + + default: + logger.V(2).Info("unknown phase", "phase", migration.Status.Phase) + return false, nil + } +} + +func (c *Controller) reconcilePreparing(ctx context.Context, migration *migrationv1alpha1.LogicalClusterMigration) (bool, error) { + logger := klog.FromContext(ctx) + lcName := logicalcluster.Name(migration.Spec.LogicalCluster) + + logger.V(2).Info("preparing origin for migration", "logicalCluster", lcName) + + // Set the origin shard in the status first and requeue to ensure it is set, + // otherwise the process stalls if an error happens intermittently as the LC + // will be purged from the informer stores. + if migration.Status.OriginShard == "" { + migration.Status.OriginShard = c.shardName + return true, nil + } + + // Annotate the LC + lc, err := c.logicalClusterLister.Cluster(lcName).Get(corev1alpha1.LogicalClusterName) + if err != nil { + return true, fmt.Errorf("failed to get LogicalCluster %s: %w", lcName, err) + } + + migrationPath := logicalcluster.From(migration).Path().Join(migration.Name).String() + + // Verify the LC is not already migrating + if existingAnn, ok := lc.Annotations[MigratingAnnotationKey]; ok && existingAnn != "" && existingAnn != migrationPath { + // This is a failstate - if a migration is created for an LC + // that already is in a migration the process cannot start. + conditions.MarkFalse( + migration, + migrationv1alpha1.LCMigrationOriginReady, + "LogicalClusterAlreadyMigrating", + conditionsv1alpha1.ConditionSeverityError, + "logical cluster is already migrating with %q", existingAnn, + ) + migration.Status.Phase = migrationv1alpha1.LogicalClusterMigrationPhaseFailed + return false, nil + } + + // Set the migration path as the migration annotation + if lc.Annotations[MigratingAnnotationKey] != migrationPath { + lcCopy := lc.DeepCopy() + if lcCopy.Annotations == nil { + lcCopy.Annotations = make(map[string]string) + } + lcCopy.Annotations[MigratingAnnotationKey] = migrationPath + if _, err := c.kcpClusterClient.CoreV1alpha1().LogicalClusters().Cluster(lcName.Path()).Update(ctx, lcCopy, metav1.UpdateOptions{}); err != nil { + return true, fmt.Errorf("failed to set migrating annotation on LogicalCluster %s: %w", lcName, err) + } + } + + // Register in MigratingLogicalClusters, this causes the handler to + // prevent deny access and the DDSIF to ignore objects related to + // the LC + if !c.migratingLogicalClusters.IsMigrating(lcName) { + if err := c.migratingLogicalClusters.Set(lcName, migrationPath); err != nil { + return false, err + } + } + + // Cancel per-cluster context to stop all requests to it + // Just passing .Path is fine since lcName is the logical cluster id. + c.cancelLogicalClusterConnections(lcName.Path(), errors.New("logical cluster is being migrated")) + + // Purge informer stores for this cluster + c.ddsif.PurgeCluster(lcName) + + // Phase is done, update LCMigration + conditions.MarkTrue(migration, migrationv1alpha1.LCMigrationOriginReady) + migration.Status.Phase = migrationv1alpha1.LogicalClusterMigrationPhaseMigrating + + logger.V(2).Info("origin prepared, transitioning to Migrating", "logicalCluster", lcName) + return false, nil +} + +func (c *Controller) reconcileMigrating(ctx context.Context, migration *migrationv1alpha1.LogicalClusterMigration) (bool, error) { + logger := klog.FromContext(ctx) + lcName := logicalcluster.Name(migration.Spec.LogicalCluster) + + // Register in MigratingLogicalClusters to prevent reconciles + migrationPath := logicalcluster.From(migration).Path().Join(migration.Name).String() + if !c.migratingLogicalClusters.IsMigrating(lcName) { + if err := c.migratingLogicalClusters.Set(lcName, migrationPath); err != nil { + return false, err + } + } + + // Purge local informer stores - this should be a noop but just to + // be safe. + c.ddsif.PurgeCluster(lcName) + + if err := c.copyDataFromOrigin(ctx, lcName, migration.Status.OriginShard); err != nil { + conditions.MarkFalse( + migration, + migrationv1alpha1.LCMigrationDataCopied, + "CopyFailed", + conditionsv1alpha1.ConditionSeverityError, + "%v", err, + ) + return true, err + } + + // Transition to OriginCleanup. + conditions.MarkTrue(migration, migrationv1alpha1.LCMigrationDataCopied) + migration.Status.Phase = migrationv1alpha1.LogicalClusterMigrationPhaseOriginCleanup + + logger.V(2).Info("data copied, transitioning to OriginCleanup", "logicalCluster", lcName) + return false, nil +} + +func (c *Controller) reconcileOriginCleanup(ctx context.Context, migration *migrationv1alpha1.LogicalClusterMigration) (bool, error) { + logger := klog.FromContext(ctx) + lcName := logicalcluster.Name(migration.Spec.LogicalCluster) + + if err := c.deleteOriginData(ctx, lcName); err != nil { + conditions.MarkFalse( + migration, + migrationv1alpha1.LCMigrationOriginCleaned, + "CleanupFailed", + conditionsv1alpha1.ConditionSeverityError, + "%v", err, + ) + return true, err + } + + // lc is gone from shard + c.migratingLogicalClusters.Remove(lcName) + + // Transition to DestinationFinalize. + conditions.MarkTrue(migration, migrationv1alpha1.LCMigrationOriginCleaned) + migration.Status.Phase = migrationv1alpha1.LogicalClusterMigrationPhaseDestinationFinalize + + logger.V(2).Info("origin cleaned, transitioning to DestinationFinalize", "logicalCluster", lcName) + return false, nil +} + +func (c *Controller) reconcileDestinationFinalize(ctx context.Context, migration *migrationv1alpha1.LogicalClusterMigration) (bool, error) { + logger := klog.FromContext(ctx) + lcName := logicalcluster.Name(migration.Spec.LogicalCluster) + + // While copying the data the logical cluster object was copied as + // well with the migration annotation which now can be dropped. + // + // The annotation is only used at the start of the migration to + // ensure that there aren't two different migrations targeting the + // same logical cluster at the same time. + // This could also be solved by requiring the + // LogicalClusterMigration to be a singleton within the logical + // cluster (much like the LogicalCluster itself), but that would + // require additional logic to still allow these events through for + // the migration reconciler to work. + // Bypass the lister: the local DDSIF for this shard has been ignoring + // objects for the migrating cluster, so the LogicalCluster that was + // just written to etcd via the dump is not yet visible through the + // lister. Read it directly from the API instead. + lc, err := c.kcpClusterClient.CoreV1alpha1().LogicalClusters().Cluster(lcName.Path()).Get(ctx, corev1alpha1.LogicalClusterName, metav1.GetOptions{}) + if err != nil { + return true, fmt.Errorf("failed to get LogicalCluster %s: %w", lcName, err) + } + if lc.Annotations[MigratingAnnotationKey] != "" { + lcCopy := lc.DeepCopy() + delete(lcCopy.Annotations, MigratingAnnotationKey) + if _, err := c.kcpClusterClient.CoreV1alpha1().LogicalClusters().Cluster(lcName.Path()).Update(ctx, lcCopy, metav1.UpdateOptions{}); err != nil { + return true, fmt.Errorf("failed to remove migrating annotation from LogicalCluster %s: %w", lcName, err) + } + } + + // Migration is done, allow access to the logical cluster. + // Maybe needs some targeted relisting for api sharing before + // allowing access so the stateful objects (*EndpointSlice) are + // updated? + c.migratingLogicalClusters.Remove(lcName) + + // Update DDSIF for kcp reconcilers to pick the LC up. + // TODO(ntnn): This might be a bit expensive, especially in large + // clusters. Relisting every ~10h is one thing, but if a shard + // contains 10000 workspaces and half of them are migrated elsewhere + // this would mean 5000 relists. + // Manually filling the stores would be least expensive but would + // easily break and seems hacky. + // Maybe LCMigration allows specifying a set of LCs to migrate + // instead of just one? Or LCMigration allows setting whether a full + // relist should be triggered after finalizing? + c.ddsif.ForceRelist() + + // Transition to Completed. + conditions.MarkTrue(migration, migrationv1alpha1.LCMigrationCompleted) + migration.Status.Phase = migrationv1alpha1.LogicalClusterMigrationPhaseCompleted + + logger.V(2).Info("migration completed", "logicalCluster", lcName) + return false, nil +} From 6339f242b67cf482eb92f98c3d2268545a828ef3 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:34:39 +0200 Subject: [PATCH 15/26] Add LogicalClusterDump handler Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/server/filters/migrationdump.go | 36 +++++ pkg/server/migrationdump/server.go | 231 ++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 pkg/server/filters/migrationdump.go create mode 100644 pkg/server/migrationdump/server.go diff --git a/pkg/server/filters/migrationdump.go b/pkg/server/filters/migrationdump.go new file mode 100644 index 00000000000..e6f9571798b --- /dev/null +++ b/pkg/server/filters/migrationdump.go @@ -0,0 +1,36 @@ +/* +Copyright 2026 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 filters + +import ( + "net/http" +) + +const migrationDumpHandlerPath = "/apis/migration.kcp.io/v1alpha1/logicalclusterdumps" + +// WithMigrationDumpHandler intercepts POSTs to the LogicalClusterDump path +// before the kube REST chain rejects the unknown migration.kcp.io API group +// with a 503. All other requests fall through to next. +func WithMigrationDumpHandler(next http.Handler, dump http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodPost && req.URL.Path == migrationDumpHandlerPath { + dump.ServeHTTP(w, req) + return + } + next.ServeHTTP(w, req) + } +} diff --git a/pkg/server/migrationdump/server.go b/pkg/server/migrationdump/server.go new file mode 100644 index 00000000000..3a691dd8e18 --- /dev/null +++ b/pkg/server/migrationdump/server.go @@ -0,0 +1,231 @@ +/* +Copyright 2026 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 migrationdump + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "slices" + "strings" + + clientv3 "go.etcd.io/etcd/client/v3" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/klog/v2" + + "github.com/kcp-dev/logicalcluster/v3" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + + bootstrappolicy "github.com/kcp-dev/kcp/pkg/authorization/bootstrap" +) + +// etcdScanPageSize is the number of keys fetched per etcd Range request when +// scanning the storage for the keys of a logical cluster. +const etcdScanPageSize int64 = 1000 + +var ( + errorScheme = runtime.NewScheme() + errorCodecs = serializer.NewCodecFactory(errorScheme) +) + +func init() { + errorScheme.AddUnversionedTypes(metav1.Unversioned, &metav1.Status{}) +} + +// migratingClusters is a local interface for checking if a cluster is migrating. +type migratingClusters interface { + IsMigrating(name logicalcluster.Name) bool +} + +// Handler serves POST requests to HandlerPath by dumping every etcd entry +// that belongs to the cluster carried in the request context. +type Handler struct { + etcdClient *clientv3.Client + etcdStoragePrefix string + migratingLogicalClusters migratingClusters +} + +func NewHandler( + etcdClient *clientv3.Client, + etcdStoragePrefix string, + migratingLogicalClusters migratingClusters, +) *Handler { + return &Handler{ + etcdClient: etcdClient, + etcdStoragePrefix: etcdStoragePrefix, + migratingLogicalClusters: migratingLogicalClusters, + } +} + +func (h *Handler) Close() error { + return h.etcdClient.Close() +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := klog.FromContext(ctx) + + if r.Method != http.MethodPost { + writeError(w, r, apierrors.NewMethodNotSupported( + migrationv1alpha1.Resource("logicalclusterdumps"), + r.Method, + )) + return + } + + cluster := genericapirequest.ClusterFrom(ctx) + if cluster == nil || cluster.Name.Empty() { + writeError(w, r, apierrors.NewBadRequest("no cluster in context")) + return + } + + user, ok := genericapirequest.UserFrom(ctx) + if !ok || user == nil { + writeError(w, r, apierrors.NewUnauthorized("no user info")) + return + } + if !slices.Contains(user.GetGroups(), bootstrappolicy.SystemExternalLogicalClusterAdmin) { + writeError(w, r, apierrors.NewForbidden( + migrationv1alpha1.Resource("logicalclusterdumps"), + "", + fmt.Errorf("user is not in group %s", bootstrappolicy.SystemExternalLogicalClusterAdmin), + )) + return + } + + if !h.migratingLogicalClusters.IsMigrating(cluster.Name) { + writeError(w, r, apierrors.NewNotFound( + migrationv1alpha1.Resource("logicalclusterdumps"), + string(cluster.Name), + )) + return + } + + // Decode the request body. Spec is empty today so we don't actually + // need anything from it, but parsing it ensures the client sent + // something resembling the expected type. + var dump migrationv1alpha1.LogicalClusterDump + if r.ContentLength != 0 { + if err := json.NewDecoder(r.Body).Decode(&dump); err != nil { + writeError(w, r, apierrors.NewBadRequest(fmt.Sprintf("failed to decode request body: %v", err))) + return + } + } + + logger.V(2).Info("dumping logical cluster from etcd", "cluster", cluster.Name) + + entries, err := scanEtcdEntries(ctx, h.etcdClient, h.etcdStoragePrefix, cluster.Name) + if err != nil { + writeError(w, r, apierrors.NewInternalError(fmt.Errorf("failed to dump logical cluster: %w", err))) + return + } + + resp := &migrationv1alpha1.LogicalClusterDump{ + TypeMeta: metav1.TypeMeta{ + APIVersion: migrationv1alpha1.SchemeGroupVersion.String(), + Kind: "LogicalClusterDump", + }, + ObjectMeta: dump.ObjectMeta, + Status: migrationv1alpha1.LogicalClusterDumpStatus{ + Entries: entries, + }, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + if err := json.NewEncoder(w).Encode(resp); err != nil { + logger.Error(err, "failed to write dump response") + } +} + +// scanEtcdEntries returns every etcd key/value pair belonging to the given +// logical cluster. +// +// TODO: stream entries as NDJSON so the response doesn't have to be buffered +// in memory. +// +// TODO: paginate via spec.Continue / status.Continue so a single dump call +// doesn't have to hold all keys in memory. +func scanEtcdEntries(ctx context.Context, etcdClient *clientv3.Client, storagePrefix string, target logicalcluster.Name) ([]migrationv1alpha1.EtcdEntry, error) { + prefix := storagePrefix + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + var entries []migrationv1alpha1.EtcdEntry + key := prefix + for { + resp, err := etcdClient.Get(ctx, key, + clientv3.WithRange(clientv3.GetPrefixRangeEnd(prefix)), + clientv3.WithLimit(etcdScanPageSize), + ) + if err != nil { + return nil, fmt.Errorf("failed to list etcd keys: %w", err) + } + + for _, kv := range resp.Kvs { + if err := ctx.Err(); err != nil { + return nil, err + } + if !belongsToLogicalCluster(prefix, string(kv.Key), target) { + continue + } + entries = append(entries, migrationv1alpha1.EtcdEntry{ + Key: strings.TrimPrefix(string(kv.Key), prefix), + Value: append([]byte(nil), kv.Value...), + }) + } + + if !resp.More { + return entries, nil + } + key = string(resp.Kvs[len(resp.Kvs)-1].Key) + "\x00" + } +} + +// belongsToLogicalCluster reports whether an etcd key belongs to the given logical cluster. +func belongsToLogicalCluster(prefix, key string, target logicalcluster.Name) bool { + rest := strings.TrimPrefix(key, prefix) + if rest == key { + return false + } + parts := strings.SplitN(rest, "/", 5) + if len(parts) < 3 { + return false + } + // ///customresources//... + if parts[2] == "customresources" { + if len(parts) < 4 { + return false + } + return logicalcluster.Name(parts[3]) == target + } + // ////... + return logicalcluster.Name(parts[2]) == target +} + +func writeError(w http.ResponseWriter, r *http.Request, err error) { + responsewriters.ErrorNegotiated(err, errorCodecs, schema.GroupVersion{}, w, r) +} From 0ac0e906209c8bcba3551497ca5d4610d710de92 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:42:05 +0200 Subject: [PATCH 16/26] Add migrating VW Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../migratingworkspaces/builder/build.go | 189 ++++++++++++++++++ pkg/virtual/migratingworkspaces/doc.go | 34 ++++ .../migratingworkspaces/options/options.go | 57 ++++++ 3 files changed, 280 insertions(+) create mode 100644 pkg/virtual/migratingworkspaces/builder/build.go create mode 100644 pkg/virtual/migratingworkspaces/doc.go create mode 100644 pkg/virtual/migratingworkspaces/options/options.go diff --git a/pkg/virtual/migratingworkspaces/builder/build.go b/pkg/virtual/migratingworkspaces/builder/build.go new file mode 100644 index 00000000000..5c97767aec9 --- /dev/null +++ b/pkg/virtual/migratingworkspaces/builder/build.go @@ -0,0 +1,189 @@ +/* +Copyright 2026 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 builder + +import ( + "context" + "net/http" + "net/http/httputil" + "net/url" + "slices" + "strings" + + "k8s.io/apiserver/pkg/authorization/authorizer" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/client-go/rest" + transport "k8s.io/client-go/transport" + "k8s.io/klog/v2" + + "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/virtual-workspace-framework/framework" + "github.com/kcp-dev/virtual-workspace-framework/pkg/handler" + "github.com/kcp-dev/virtual-workspace-framework/pkg/rootapiserver" + + bootstrappolicy "github.com/kcp-dev/kcp/pkg/authorization/bootstrap" + "github.com/kcp-dev/kcp/pkg/virtual/migratingworkspaces" +) + +func BuildVirtualWorkspace( + cfg *rest.Config, + rootPathPrefix string, +) ([]rootapiserver.NamedVirtualWorkspace, error) { + if !strings.HasSuffix(rootPathPrefix, "/") { + rootPathPrefix += "/" + } + + // Build clients that talk directly to the local shard. + localCfg := rest.CopyConfig(cfg) + + vw := &handler.VirtualWorkspace{ + RootPathResolver: framework.RootPathResolverFunc(func(urlPath string, requestContext context.Context) (accepted bool, prefixToStrip string, completedContext context.Context) { + cluster, prefixToStrip, ok := digestURL(urlPath, rootPathPrefix) + if !ok { + return false, "", requestContext + } + + if cluster.Wildcard { + return false, "", requestContext + } + + completedContext = genericapirequest.WithCluster(requestContext, cluster) + return true, prefixToStrip, completedContext + }), + Authorizer: authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + if a.GetUser() == nil { + return authorizer.DecisionDeny, "no user info", nil + } + if !slices.Contains(a.GetUser().GetGroups(), bootstrappolicy.SystemExternalLogicalClusterAdmin) { + return authorizer.DecisionDeny, "user is not in group " + bootstrappolicy.SystemExternalLogicalClusterAdmin, nil + } + return authorizer.DecisionAllow, "", nil + }), + ReadyChecker: framework.ReadyFunc(func() error { + return nil + }), + HandlerFactory: handler.HandlerFactory(func(rootAPIServerConfig genericapiserver.CompletedConfig) (http.Handler, error) { + return newMigratingHandler(localCfg) + }), + } + + return []rootapiserver.NamedVirtualWorkspace{ + {Name: migratingworkspaces.VirtualWorkspaceName, VirtualWorkspace: vw}, + }, nil +} + +// migratingHandler is a narrow reverse proxy that forwards the +// LogicalClusterDump endpoint to the local shard. Anything else under the +// VW prefix is rejected. +type migratingHandler struct { + proxy *httputil.ReverseProxy + host string +} + +func newMigratingHandler(cfg *rest.Config) (http.Handler, error) { + target, err := url.Parse(cfg.Host) + if err != nil { + return nil, err + } + transport, err := rest.TransportFor(cfg) + if err != nil { + return nil, err + } + proxy := &httputil.ReverseProxy{ + Transport: transport, + Director: func(*http.Request) {}, // request is fully rewritten before ServeHTTP forwards + } + return &migratingHandler{proxy: proxy, host: target.Host}, nil +} + +func (h *migratingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + logger := klog.FromContext(ctx) + + cluster := genericapirequest.ClusterFrom(ctx) + if cluster == nil || cluster.Name.Empty() { + http.Error(w, "no cluster in context", http.StatusBadRequest) + return + } + + if req.URL.Path != migratingworkspaces.HandlerPath { + http.NotFound(w, req) + return + } + + outReq := req.Clone(ctx) + outReq.URL.Scheme = "https" + outReq.URL.Host = h.host + outReq.URL.Path = cluster.Name.Path().RequestPath() + migratingworkspaces.HandlerPath + outReq.Host = h.host + outReq.RequestURI = "" + + // Forward the caller's identity via impersonation so the dump handler + // sees the real user (e.g. system:kcp:external-logical-cluster-admin). + if userInfo, ok := genericapirequest.UserFrom(ctx); ok { + outReq.Header.Set(transport.ImpersonateUserHeader, userInfo.GetName()) + for _, group := range userInfo.GetGroups() { + outReq.Header.Add(transport.ImpersonateGroupHeader, group) + } + } + + logger.V(2).Info("migrating VW proxying dump", "cluster", cluster.Name, "url", outReq.URL.String()) + h.proxy.ServeHTTP(w, outReq) +} + +func digestURL(urlPath, rootPathPrefix string) ( + cluster genericapirequest.Cluster, + logicalPath string, + accepted bool, +) { + if !strings.HasPrefix(urlPath, rootPathPrefix) { + return genericapirequest.Cluster{}, "", false + } + withoutRootPathPrefix := strings.TrimPrefix(urlPath, rootPathPrefix) + + // Incoming requests look like: + // /services/migratingworkspaces/clusters//apis/... + // └─── withoutRootPathPrefix + if !strings.HasPrefix(withoutRootPathPrefix, "clusters/") { + return genericapirequest.Cluster{}, "", false + } + + withoutClustersPrefix := strings.TrimPrefix(withoutRootPathPrefix, "clusters/") + parts := strings.SplitN(withoutClustersPrefix, "/", 2) + clusterPath := logicalcluster.NewPath(parts[0]) + + realPath := "/" + if len(parts) > 1 { + realPath += parts[1] + } + + cluster = genericapirequest.Cluster{} + if clusterPath == logicalcluster.Wildcard { + cluster.Wildcard = true + } else { + var ok bool + cluster.Name, ok = clusterPath.Name() + if !ok { + return genericapirequest.Cluster{}, "", false + } + } + + // Strip the entire prefix including the cluster path, leaving just the + // kube API path (e.g. /api/v1/configmaps). + return cluster, strings.TrimSuffix(urlPath, realPath), true +} diff --git a/pkg/virtual/migratingworkspaces/doc.go b/pkg/virtual/migratingworkspaces/doc.go new file mode 100644 index 00000000000..f7dbf753482 --- /dev/null +++ b/pkg/virtual/migratingworkspaces/doc.go @@ -0,0 +1,34 @@ +/* +Copyright 2026 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 migratingworkspaces provides a virtual workspace that allows +// other shards to bypass the front-proxy to pull data of a logical +// cluster to be migrated directly from the origin shard. +// It only allows system:kcp:external-logical-cluster-admin access to +// the LogicalClusterDump in the migration.kcp.io API group. +package migratingworkspaces + +import "path" + +const VirtualWorkspaceName string = "migratingworkspaces" + +// HandlerPath is the URL path that the dump handler responds to. +const HandlerPath = "/apis/migration.kcp.io/v1alpha1/logicalclusterdumps" + +// URLFor returns the absolute path prefix for the migrating workspaces VW. +func URLFor() string { + return path.Join("/services", VirtualWorkspaceName) +} diff --git a/pkg/virtual/migratingworkspaces/options/options.go b/pkg/virtual/migratingworkspaces/options/options.go new file mode 100644 index 00000000000..809750467e4 --- /dev/null +++ b/pkg/virtual/migratingworkspaces/options/options.go @@ -0,0 +1,57 @@ +/* +Copyright 2026 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 options + +import ( + "path" + + "github.com/spf13/pflag" + + "k8s.io/client-go/rest" + + "github.com/kcp-dev/virtual-workspace-framework/pkg/rootapiserver" + + "github.com/kcp-dev/kcp/pkg/virtual/migratingworkspaces" + "github.com/kcp-dev/kcp/pkg/virtual/migratingworkspaces/builder" +) + +type MigratingWorkspaces struct{} + +func New() *MigratingWorkspaces { + return &MigratingWorkspaces{} +} + +func (o *MigratingWorkspaces) AddFlags(flags *pflag.FlagSet, prefix string) { + if o == nil { + return + } +} + +func (o *MigratingWorkspaces) Validate(flagPrefix string) []error { + if o == nil { + return nil + } + return nil +} + +func (o *MigratingWorkspaces) NewVirtualWorkspaces( + rootPathPrefix string, + config *rest.Config, +) ([]rootapiserver.NamedVirtualWorkspace, error) { + config = rest.AddUserAgent(rest.CopyConfig(config), migratingworkspaces.VirtualWorkspaceName+"-virtual-workspace") + return builder.BuildVirtualWorkspace(config, path.Join(rootPathPrefix, migratingworkspaces.VirtualWorkspaceName)) +} From 429d91ddddcb022476ba054e8a23b2ba5f8b18f6 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:42:21 +0200 Subject: [PATCH 17/26] Add WithBlockMigratingLogicalClusters and wire it Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/server/config.go | 3 + pkg/server/filters/migratinglogicalcluster.go | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 pkg/server/filters/migratinglogicalcluster.go diff --git a/pkg/server/config.go b/pkg/server/config.go index 2d4ba264154..e92548a4d6e 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -579,6 +579,9 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co // 2. Rest of the handlers up to Authz // 3. Scoping handlers to ensure that the request is scoped to the user's clusters before authz is done. // 4. Rest of the handlers. + if kcpfeatures.DefaultFeatureGate.Enabled(kcpfeatures.LogicalClusterMigration) { + apiHandler = kcpfilters.WithBlockMigratingLogicalClusters(apiHandler, c.MigratingLogicalClusters.IsMigrating) + } apiHandler = kcpfilters.WithImpersonationScoping(apiHandler) apiHandler = genericapiserver.DefaultBuildHandlerChainFromImpersonationToAuthz(apiHandler, genericConfig) apiHandler = kcpfilters.WithImpersonationGatekeeper(apiHandler) diff --git a/pkg/server/filters/migratinglogicalcluster.go b/pkg/server/filters/migratinglogicalcluster.go new file mode 100644 index 00000000000..842f75db801 --- /dev/null +++ b/pkg/server/filters/migratinglogicalcluster.go @@ -0,0 +1,64 @@ +/* +Copyright 2026 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 filters + +import ( + "net/http" + "slices" + + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/klog/v2" + + "github.com/kcp-dev/logicalcluster/v3" + + bootstrappolicy "github.com/kcp-dev/kcp/pkg/authorization/bootstrap" +) + +// WithBlockMigratingLogicalClusters rejects requests to logical clusters that are currently being migrated. +// +// This is very similar to WithBlockInactiveLogicalClusters, however the +// migration requires that no client except other shards can access the +// logical cluster, otherwise operators running with admin rights might +// modify objects after they were migrated, producing an inconsistent +// state. +func WithBlockMigratingLogicalClusters(handler http.Handler, isMigrating func(logicalcluster.Name) bool) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + cluster := request.ClusterFrom(req.Context()) + if cluster == nil { + handler.ServeHTTP(w, req) + return + } + + if !isMigrating(cluster.Name) { + handler.ServeHTTP(w, req) + return + } + + userInfo, ok := request.UserFrom(req.Context()) + if ok && slices.Contains(userInfo.GetGroups(), bootstrappolicy.SystemExternalLogicalClusterAdmin) { + // allow system:kcp:external-logical-cluster-admin to pass, + // required for the destination shard to get + // logicalclusterdump from the migrating workspace + handler.ServeHTTP(w, req) + return + } + + klog.FromContext(req.Context()).V(2).Info("migrating filter: blocking request to migrating cluster", "cluster", cluster.Name) + w.Header().Set("Retry-After", "1") + http.Error(w, "logical cluster is being migrated", http.StatusServiceUnavailable) + } +} From 4cb25c7450ce1babeffdbd70d7b981fc670f51f3 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:44:17 +0200 Subject: [PATCH 18/26] Wire in migrating VW Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/virtual/options/options.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/virtual/options/options.go b/pkg/virtual/options/options.go index 37b8c71a34c..68ff2f095bd 100644 --- a/pkg/virtual/options/options.go +++ b/pkg/virtual/options/options.go @@ -30,6 +30,7 @@ import ( apiexportoptions "github.com/kcp-dev/kcp/pkg/virtual/apiexport/options" apiresourceschemaoptions "github.com/kcp-dev/kcp/pkg/virtual/apiresourceschema/options" initializingworkspacesoptions "github.com/kcp-dev/kcp/pkg/virtual/initializingworkspaces/options" + migratingworkspacesoptions "github.com/kcp-dev/kcp/pkg/virtual/migratingworkspaces/options" replicationoptions "github.com/kcp-dev/kcp/pkg/virtual/replication/options" terminatingworkspaceoptions "github.com/kcp-dev/kcp/pkg/virtual/terminatingworkspaces/options" ) @@ -40,6 +41,7 @@ type Options struct { APIExport *apiexportoptions.APIExport APIResourceSchema *apiresourceschemaoptions.APIResourceSchema InitializingWorkspaces *initializingworkspacesoptions.InitializingWorkspaces + MigratingWorkspaces *migratingworkspacesoptions.MigratingWorkspaces TerminatingWorkspaces *terminatingworkspaceoptions.TerminatingWorkspaces } @@ -48,6 +50,7 @@ func NewOptions() *Options { APIExport: apiexportoptions.New(), APIResourceSchema: apiresourceschemaoptions.New(), InitializingWorkspaces: initializingworkspacesoptions.New(), + MigratingWorkspaces: migratingworkspacesoptions.New(), TerminatingWorkspaces: terminatingworkspaceoptions.New(), } } @@ -58,6 +61,7 @@ func (o *Options) Validate() []error { errs = append(errs, o.APIExport.Validate(virtualWorkspacesFlagPrefix)...) errs = append(errs, o.APIResourceSchema.Validate(virtualWorkspacesFlagPrefix)...) errs = append(errs, o.InitializingWorkspaces.Validate(virtualWorkspacesFlagPrefix)...) + errs = append(errs, o.MigratingWorkspaces.Validate(virtualWorkspacesFlagPrefix)...) errs = append(errs, o.TerminatingWorkspaces.Validate(virtualWorkspacesFlagPrefix)...) return errs @@ -65,6 +69,7 @@ func (o *Options) Validate() []error { func (o *Options) AddFlags(fs *pflag.FlagSet) { o.InitializingWorkspaces.AddFlags(fs, virtualWorkspacesFlagPrefix) + o.MigratingWorkspaces.AddFlags(fs, virtualWorkspacesFlagPrefix) o.TerminatingWorkspaces.AddFlags(fs, virtualWorkspacesFlagPrefix) o.APIExport.AddFlags(fs, virtualWorkspacesFlagPrefix) o.APIResourceSchema.AddFlags(fs, virtualWorkspacesFlagPrefix) @@ -116,7 +121,12 @@ func (o *Options) NewVirtualWorkspaces( return nil, err } - all, err := Merge(apiexports, apiresourceschemas, initializingworkspaces, replications, terminatingworkspaces) + migratingworkspaces, err := o.MigratingWorkspaces.NewVirtualWorkspaces(rootPathPrefix, config) + if err != nil { + return nil, err + } + + all, err := Merge(apiexports, apiresourceschemas, initializingworkspaces, replications, terminatingworkspaces, migratingworkspaces) if err != nil { return nil, err } From 04be05cae42d61d9e866f182b716b9f691e96df5 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:46:13 +0200 Subject: [PATCH 19/26] Wire LogicalClusterDump handler Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/server/config.go | 25 ++++++++++++++++++++----- pkg/server/server.go | 7 +++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pkg/server/config.go b/pkg/server/config.go index e92548a4d6e..c12ac514845 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -78,6 +78,7 @@ import ( "github.com/kcp-dev/kcp/pkg/server/aggregatingcrdversiondiscovery" "github.com/kcp-dev/kcp/pkg/server/bootstrap" kcpfilters "github.com/kcp-dev/kcp/pkg/server/filters" + "github.com/kcp-dev/kcp/pkg/server/migrationdump" "github.com/kcp-dev/kcp/pkg/server/openapiv3" kcpserveroptions "github.com/kcp-dev/kcp/pkg/server/options" "github.com/kcp-dev/kcp/pkg/server/options/batteries" @@ -132,12 +133,13 @@ type ExtraConfig struct { ExternalLogicalClusterAdminConfig *rest.Config // client config connecting to the front proxy // misc - preHandlerChainMux *handlerChainMuxes - quotaAdmissionStopCh chan struct{} - ClusterContextManager *contextmanager.Manager[logicalcluster.Path] + preHandlerChainMux *handlerChainMuxes + quotaAdmissionStopCh chan struct{} + ClusterContextManager *contextmanager.Manager[logicalcluster.Path] MigratingLogicalClusters *logicalclustermigration.MigratingLogicalClusters - openAPIv3Controller *openapiv3.Controller - openAPIv3ServiceCache *openapiv3.ServiceCache + MigrationDumpHandler *migrationdump.Handler + openAPIv3Controller *openapiv3.Controller + openAPIv3ServiceCache *openapiv3.ServiceCache // URL getters depending on genericspiserver.ExternalAddress which is initialized on server run ShardBaseURL func() string @@ -580,6 +582,7 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co // 3. Scoping handlers to ensure that the request is scoped to the user's clusters before authz is done. // 4. Rest of the handlers. if kcpfeatures.DefaultFeatureGate.Enabled(kcpfeatures.LogicalClusterMigration) { + apiHandler = kcpfilters.WithMigrationDumpHandler(apiHandler, c.MigrationDumpHandler) apiHandler = kcpfilters.WithBlockMigratingLogicalClusters(apiHandler, c.MigratingLogicalClusters.IsMigrating) } apiHandler = kcpfilters.WithImpersonationScoping(apiHandler) @@ -802,6 +805,18 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co } } + if kcpfeatures.DefaultFeatureGate.Enabled(kcpfeatures.LogicalClusterMigration) { + etcdClient, err := newEtcdClient(c.Options.GenericControlPlane.Etcd.StorageConfig.Transport) + if err != nil { + return nil, fmt.Errorf("failed to create etcd client for migration dump handler: %w", err) + } + c.ExtraConfig.MigrationDumpHandler = migrationdump.NewHandler( + etcdClient, + c.Options.GenericControlPlane.Etcd.StorageConfig.Prefix, + c.ExtraConfig.MigratingLogicalClusters, + ) + } + c.openAPIv3Controller = openapiv3.NewController(c.ApiExtensionsSharedInformerFactory.Apiextensions().V1().CustomResourceDefinitions()) c.openAPIv3ServiceCache = openapiv3.NewServiceCache(c.GenericConfig.OpenAPIV3Config, c.ApiExtensions.ExtraConfig.ClusterAwareCRDLister, c.openAPIv3Controller, openapiv3.DefaultServiceCacheSize) diff --git a/pkg/server/server.go b/pkg/server/server.go index 16b58c5123d..bf4adaefad6 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -714,6 +714,13 @@ func (s *Server) Run(ctx context.Context) error { }); err != nil { return err } + if s.MigrationDumpHandler != nil { + if err := s.AddPreShutdownHook("kcp-migration-dump-etcd-client", func() error { + return s.MigrationDumpHandler.Close() + }); err != nil { + return err + } + } if len(s.Options.Cache.Client.KubeconfigFile) == 0 { if err := s.installCacheServer(ctx); err != nil { return err From 11666ee94e6b6a74d51dd48d2a236e5d5e9d27a1 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:48:00 +0200 Subject: [PATCH 20/26] Skip adding per-lc context to LogicalClusterDump requests Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/server/filters/perclustercontext.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/server/filters/perclustercontext.go b/pkg/server/filters/perclustercontext.go index 36e7e4e58fc..21117b48bab 100644 --- a/pkg/server/filters/perclustercontext.go +++ b/pkg/server/filters/perclustercontext.go @@ -46,6 +46,10 @@ func WithPerClusterContext(handler http.Handler, mgr *contextmanager.Manager[log "/openapi", // logical cluster objects must still be editable to lifecylce the lc. "/apis/core.kcp.io/v1alpha1/logicalclusters", + // Skip adding contexts to requests with LogicalClusterDump, + // contexts for migrating LCs are cancelled and stay cancelled + // until they are migrated. + migrationDumpHandlerPath, } return func(w http.ResponseWriter, req *http.Request) { From 8bffeaca17aa7a6d7f063063de9cd4ff8b198a87 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:48:44 +0200 Subject: [PATCH 21/26] Wire logicalclustermigration.Controller into server Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/server/controllers.go | 78 +++++++++++++++++++++++++++++++++++++++ pkg/server/server.go | 6 +++ 2 files changed, 84 insertions(+) diff --git a/pkg/server/controllers.go b/pkg/server/controllers.go index 7ea53c57a1d..636fe4541ec 100644 --- a/pkg/server/controllers.go +++ b/pkg/server/controllers.go @@ -18,12 +18,16 @@ package server import ( "context" + "crypto/tls" "errors" "fmt" "os" "strings" "time" + "go.etcd.io/etcd/client/pkg/v3/transport" + clientv3 "go.etcd.io/etcd/client/v3" + corev1 "k8s.io/api/core/v1" apiextensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,6 +36,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" pluginvalidatingadmissionpolicy "k8s.io/apiserver/pkg/admission/plugin/policy/validating" "k8s.io/apiserver/pkg/cel/openapi/resolver" + "k8s.io/apiserver/pkg/storage/storagebackend" "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/memory" k8sscheme "k8s.io/client-go/kubernetes/scheme" @@ -91,6 +96,7 @@ import ( "github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper" "github.com/kcp-dev/kcp/pkg/reconciler/garbagecollector" "github.com/kcp-dev/kcp/pkg/reconciler/kubequota" + "github.com/kcp-dev/kcp/pkg/reconciler/migrating/logicalclustermigration" "github.com/kcp-dev/kcp/pkg/reconciler/tenancy/bootstrap" "github.com/kcp-dev/kcp/pkg/reconciler/tenancy/defaultapibindinglifecycle" "github.com/kcp-dev/kcp/pkg/reconciler/tenancy/initialization" @@ -1126,6 +1132,78 @@ func (s *Server) installLogicalClusterCleanupController(ctx context.Context, con }) } +func (s *Server) installLogicalClusterMigrationController(ctx context.Context) error { + externalConfig := rest.CopyConfig(s.ExternalLogicalClusterAdminConfig) + externalConfig = rest.AddUserAgent(externalConfig, logicalclustermigration.ControllerName) + + kcpClusterClient, err := kcpclientset.NewForConfig(externalConfig) + if err != nil { + return err + } + + etcdClient, err := s.newEtcdClient() + if err != nil { + return err + } + + c, err := logicalclustermigration.NewController( + s.Options.Extra.ShardName, + kcpClusterClient, + s.ExternalLogicalClusterAdminConfig, + etcdClient, + s.Options.GenericControlPlane.Etcd.StorageConfig.Prefix, + s.CacheKcpSharedInformerFactory.Migration().V1alpha1().LogicalClusterMigrations(), + s.KcpSharedInformerFactory.Core().V1alpha1().LogicalClusters(), + s.CacheKcpSharedInformerFactory.Core().V1alpha1().Shards(), + s.MigratingLogicalClusters, + s.ClusterContextManager.Cancel, + s.PartialMetadataDDSIF, + ) + if err != nil { + return err + } + + return s.registerController(&controllerWrapper{ + Name: logicalclustermigration.ControllerName, + Wait: func(ctx context.Context, s *Server) error { + return wait.PollUntilContextCancel(ctx, waitPollInterval, true, func(ctx context.Context) (bool, error) { + return s.CacheKcpSharedInformerFactory.Migration().V1alpha1().LogicalClusterMigrations().Informer().HasSynced() && + s.KcpSharedInformerFactory.Core().V1alpha1().LogicalClusters().Informer().HasSynced() && + s.CacheKcpSharedInformerFactory.Core().V1alpha1().Shards().Informer().HasSynced(), nil + }) + }, + Runner: func(ctx context.Context) { + defer etcdClient.Close() + c.Start(ctx, 2) + }, + }) +} + +func (s *Server) newEtcdClient() (*clientv3.Client, error) { + return newEtcdClient(s.Options.GenericControlPlane.Etcd.StorageConfig.Transport) +} + +func newEtcdClient(transportConfig storagebackend.TransportConfig) (*clientv3.Client, error) { + var tlsConfig *tls.Config + if transportConfig.CertFile != "" || transportConfig.KeyFile != "" || transportConfig.TrustedCAFile != "" { + tlsInfo := transport.TLSInfo{ + CertFile: transportConfig.CertFile, + KeyFile: transportConfig.KeyFile, + TrustedCAFile: transportConfig.TrustedCAFile, + } + var err error + tlsConfig, err = tlsInfo.ClientConfig() + if err != nil { + return nil, fmt.Errorf("failed to create etcd TLS config: %w", err) + } + } + + return clientv3.New(clientv3.Config{ + Endpoints: transportConfig.ServerList, + TLS: tlsConfig, + }) +} + func (s *Server) installAPIExportController(ctx context.Context, config *rest.Config) error { config = rest.CopyConfig(config) config = rest.AddUserAgent(config, apiexport.ControllerName) diff --git a/pkg/server/server.go b/pkg/server/server.go index bf4adaefad6..f562aeeab09 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -305,6 +305,12 @@ func (s *Server) installControllers(ctx context.Context, controllerConfig *rest. } } + if kcpfeatures.DefaultFeatureGate.Enabled(kcpfeatures.LogicalClusterMigration) { + if err := s.installLogicalClusterMigrationController(ctx); err != nil { + return err + } + } + if s.Options.Controllers.EnableAll || enabled.Has("apibinding") { if err := s.installAPIBindingController(ctx, controllerConfig, s.PartialMetadataDDSIF); err != nil { return err From 54e6faa2cb677ba0575f35c810380044c355535b Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:49:51 +0200 Subject: [PATCH 22/26] Add LogicalClusterMigration e2e test Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../logicalclustermigration/migration_test.go | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 test/e2e/logicalclustermigration/migration_test.go diff --git a/test/e2e/logicalclustermigration/migration_test.go b/test/e2e/logicalclustermigration/migration_test.go new file mode 100644 index 00000000000..7dfeedd8836 --- /dev/null +++ b/test/e2e/logicalclustermigration/migration_test.go @@ -0,0 +1,227 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + + kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes" + "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/sdk/apis/core" + migrationv1alpha1 "github.com/kcp-dev/sdk/apis/migration/v1alpha1" + kcpclientset "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" + kcptesting "github.com/kcp-dev/sdk/testing" + + "github.com/kcp-dev/kcp/test/e2e/framework" +) + +func TestFullMigration(t *testing.T) { + t.Parallel() + framework.Suite(t, "control-plane") + + server := kcptesting.SharedKcpServer(t) + + if len(server.ShardNames()) < 2 { + t.Skip("requires multi-shard setup") + } + + cfg := server.BaseConfig(t) + kcpClusterClient, err := kcpclientset.NewForConfig(cfg) + require.NoError(t, err) + kubeClusterClient, err := kcpkubernetesclientset.NewForConfig(cfg) + require.NoError(t, err) + + shardNames := server.ShardNames() + originShard := shardNames[0] + destinationShard := shardNames[1] + + t.Logf("Using origin shard %q and destination shard %q", originShard, destinationShard) + + // Create an org workspace and a child workspace pinned to the origin shard. + orgPath, _ := kcptesting.NewWorkspaceFixture(t, server, core.RootCluster.Path()) + wsPath, ws := kcptesting.NewWorkspaceFixture(t, server, orgPath, kcptesting.WithShard(originShard)) + lcName := logicalcluster.Name(ws.Spec.Cluster) + + t.Logf("Workspace %s (logical cluster %s) created on shard %s", wsPath, lcName, originShard) + + t.Logf("Creatint test data") + numConfigMaps := 5 + for i := range numConfigMaps { + _, err := kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default").Create( + t.Context(), + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("test-cm-%d", i)}, + Data: map[string]string{"index": fmt.Sprintf("%d", i)}, + }, + metav1.CreateOptions{}, + ) + require.NoError(t, err) + } + + t.Log("Starting informer for post-migration verification") + cmClient := kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default") + informerStore, informerController := cache.NewInformerWithOptions(cache.InformerOptions{ + ListerWatcher: &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return cmClient.List(t.Context(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return cmClient.Watch(t.Context(), options) + }, + }, + ObjectType: &corev1.ConfigMap{}, + Handler: cache.ResourceEventHandlerFuncs{}, + }) + informerCtx, informerCancel := context.WithCancel(t.Context()) + t.Cleanup(informerCancel) + go informerController.Run(informerCtx.Done()) + + t.Logf("Creating LogicalClusterMigration for %s from %s to %s", lcName, originShard, destinationShard) + lcm, err := kcpClusterClient.Cluster(orgPath).MigrationV1alpha1().LogicalClusterMigrations().Create( + t.Context(), + &migrationv1alpha1.LogicalClusterMigration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-migration", + }, + Spec: migrationv1alpha1.LogicalClusterMigrationSpec{ + LogicalCluster: lcName.String(), + DestinationShard: destinationShard, + }, + }, + metav1.CreateOptions{}, + ) + require.NoError(t, err) + + t.Logf("Waiting for migration to complete") + require.EventuallyWithT(t, func(c *assert.CollectT) { + migration, err := kcpClusterClient.Cluster(orgPath).MigrationV1alpha1().LogicalClusterMigrations().Get(t.Context(), lcm.Name, metav1.GetOptions{}) + require.NoError(c, err) + t.Logf("migration phase: %q", migration.Status.Phase) + t.Logf("migration conditions: %#v", migration.Status.Conditions) + require.Equal(c, migrationv1alpha1.LogicalClusterMigrationPhaseCompleted, migration.Status.Phase) + }, wait.ForeverTestTimeout, 500*time.Millisecond, "waiting for migration to complete") + + t.Logf("Migration completed, running post-migration tests") + + // Test that a client can access the lc after the migration. + t.Run("ClientAccess", func(t *testing.T) { + t.Parallel() + + _, err := kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default").List(t.Context(), metav1.ListOptions{}) + require.NoError(t, err, "LIST should succeed after migration") + + _, err = kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default").Get(t.Context(), "test-cm-0", metav1.GetOptions{}) + require.NoError(t, err, "GET should succeed after migration") + }) + + // Verify that a RetryWatcher that was setup before the migration was + // started reconnects and delivers events after the migration. + // The RetryWatcher relists internally on 410 Gone (RV mismatch + // across shards), which is the expected behavior for clients that + // track resource versions across a migration. + t.Run("PreMigrateWatch", func(t *testing.T) { + t.Parallel() + + _, err := kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default").Create( + t.Context(), + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "post-migration-cm"}, + }, + metav1.CreateOptions{}, + ) + require.NoError(t, err) + + // The pre-migration RetryWatcher may have been terminated due to + // the RV space change (410 Gone). Establish a fresh watch to + // verify new watches work after migration. + postMigrationWatcher, err := kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default").Watch(t.Context(), metav1.ListOptions{}) + require.NoError(t, err, "should be able to establish a new watch after migration") + defer postMigrationWatcher.Stop() + + for { + select { + case <-t.Context().Done(): + t.Fatal("test context closed") + case event, ok := <-postMigrationWatcher.ResultChan(): + if !ok { + t.Fatal("post-migration watch channel closed unexpectedly") + } + if event.Type == watch.Error { + continue + } + cm, ok := event.Object.(*corev1.ConfigMap) + if ok && cm.Name == "post-migration-cm" { + return + } + case <-time.After(wait.ForeverTestTimeout): + t.Fatal("post-migration watch did not receive event") + } + } + }) + + // Verify that an informer that was setup before the migration was + // started picks up objects after the migration. + t.Run("PreMigrateInformer", func(t *testing.T) { + t.Parallel() + + _, err := kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default").Create( + t.Context(), + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "post-migration-informer-cm"}, + }, + metav1.CreateOptions{}, + ) + require.NoError(t, err) + + require.EventuallyWithT(t, func(c *assert.CollectT) { + _, exists, err := informerStore.GetByKey("default/post-migration-informer-cm") + assert.NoError(c, err) + assert.True(c, exists, "informer should have picked up post-migration-informer-cm") + }, wait.ForeverTestTimeout, 500*time.Millisecond, "waiting for informer to pick up post-migration object") + }) + + // Check that all test objects were transferred to the new shard. + t.Run("DataIntegrity", func(t *testing.T) { + t.Parallel() + + cms, err := kubeClusterClient.Cluster(wsPath).CoreV1().ConfigMaps("default").List(t.Context(), metav1.ListOptions{}) + require.NoError(t, err) + + cmNames := make(map[string]bool, len(cms.Items)) + for _, cm := range cms.Items { + cmNames[cm.Name] = true + } + + for i := range numConfigMaps { + name := fmt.Sprintf("test-cm-%d", i) + require.True(t, cmNames[name], "ConfigMap %s not found after migration", name) + } + }) +} From d133e135d821be8a4fc21eb499615ec339998433 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:50:21 +0200 Subject: [PATCH 23/26] Add integration test to ignore LCs Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- .../ignore_cluster_test.go | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test/integration/logicalclustermigration/ignore_cluster_test.go diff --git a/test/integration/logicalclustermigration/ignore_cluster_test.go b/test/integration/logicalclustermigration/ignore_cluster_test.go new file mode 100644 index 00000000000..0ec522598b1 --- /dev/null +++ b/test/integration/logicalclustermigration/ignore_cluster_test.go @@ -0,0 +1,114 @@ +/* +Copyright 2026 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 logicalclustermigration + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/sdk/apis/core" + kcptesting "github.com/kcp-dev/sdk/testing" + + "github.com/kcp-dev/kcp/test/integration/framework" +) + +var configMapGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"} + +// TestIgnoreCluster tests that adding a cluster to the migrating +// logical clusters causes all objects from that cluster to be ignored +// and inaccessible for kcp reconcilers. +func TestIgnoreCluster(t *testing.T) { + t.Parallel() + + server, kcpClientSet, kubeClient := framework.StartTestServer(t) + + workspace := kcptesting.NewLowLevelWorkspaceFixture(t, kcpClientSet, kcpClientSet, core.RootCluster.Path(), kcptesting.WithNamePrefix("ignore-cluster")) + lcName := logicalcluster.Name(workspace.Spec.Cluster) + + t.Logf("Creating ConfigMap in workspace %s", lcName) + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "test-cm"}, + Data: map[string]string{"key": "value"}, + } + + _, err := kubeClient.Cluster(lcName.Path()).CoreV1().ConfigMaps("default").Create(t.Context(), cm, metav1.CreateOptions{}) + require.NoError(t, err) + + ddsif := server.Server.PartialMetadataDDSIF + + t.Logf("Verifying ConfigMap is visible in DDSIF store") + require.EventuallyWithT(t, func(c *assert.CollectT) { + inf, err := ddsif.ForResource(configMapGVR) + require.NoError(c, err) + objs, err := inf.Lister().ByCluster(lcName).List(labels.Everything()) + require.NoError(c, err) + require.NotEmpty(c, objs, "no configmaps found in DDSIF store") + found := false + for _, obj := range objs { + accessor, err := meta.Accessor(obj) + require.NoError(c, err) + if accessor.GetName() == cm.Name { + found = true + } + } + require.True(c, found, "configmap %q not found in DDSIF store", cm.Name) + }, wait.ForeverTestTimeout, 100*time.Millisecond) + + t.Logf("Marking logical cluster %s as migrating and purging from DDSIF", lcName) + err = server.Server.MigratingLogicalClusters.Set(lcName, "test-migration") + require.NoError(t, err) + ddsif.PurgeCluster(lcName) + + t.Logf("Verifying ConfigMap is no longer visible in DDSIF store") + inf, err := ddsif.ForResource(configMapGVR) + require.NoError(t, err) + + objs, err := inf.Lister().ByCluster(lcName).List(labels.Everything()) + require.NoError(t, err) + require.Empty(t, objs) + + t.Logf("Removing logical cluster %s from migration and forcing relist", lcName) + server.Server.MigratingLogicalClusters.Remove(lcName) + ddsif.ForceRelist() + + t.Logf("Verifying ConfigMap reappears in DDSIF store after relist") + require.EventuallyWithT(t, func(c *assert.CollectT) { + objs, err := inf.Lister().ByCluster(lcName).List(labels.Everything()) + require.NoError(c, err) + require.NotEmpty(c, objs, "configmap not yet back in DDSIF store") + found := false + for _, obj := range objs { + accessor, err := meta.Accessor(obj) + require.NoError(c, err) + if accessor.GetName() == cm.Name { + found = true + } + } + require.True(c, found, "configmap %q not found in DDSIF store", cm.Name) + }, wait.ForeverTestTimeout, 100*time.Millisecond) +} From 1e3cc0fcb556987d4559eabbf23585d227d7e18d Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 07:53:05 +0200 Subject: [PATCH 24/26] Add system:kcp:external-logical-cluster-admin RBAC for LCMigration Signed-off-by: Nelo-T. Wallus Signed-off-by: Nelo-T. Wallus --- pkg/authorization/bootstrap/policy.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/authorization/bootstrap/policy.go b/pkg/authorization/bootstrap/policy.go index bd0f6e818c1..62be69300ec 100644 --- a/pkg/authorization/bootstrap/policy.go +++ b/pkg/authorization/bootstrap/policy.go @@ -27,6 +27,7 @@ import ( "github.com/kcp-dev/sdk/apis/apis" "github.com/kcp-dev/sdk/apis/cache" "github.com/kcp-dev/sdk/apis/core" + "github.com/kcp-dev/sdk/apis/migration" "github.com/kcp-dev/sdk/apis/tenancy" ) @@ -154,6 +155,16 @@ func clusterRoles() []rbacv1.ClusterRole { rbacv1helpers.NewRule("get").Groups(tenancy.GroupName).Resources("workspaceauthenticationconfigurations").RuleOrDie(), }, }, + { + // logical cluster migration + ObjectMeta: metav1.ObjectMeta{Name: SystemExternalLogicalClusterAdmin}, + Rules: []rbacv1.PolicyRule{ + // pull data from origin to destination shard during migration + rbacv1helpers.NewRule("create").Groups(migration.GroupName).Resources("logicalclusterdumps").RuleOrDie(), + // allows shards to update LogicalClusterMigrations wherever it is placed + rbacv1helpers.NewRule("get", "update", "patch").Groups(migration.GroupName).Resources("logicalclustermigrations", "logicalclustermigrations/status").RuleOrDie(), + }, + }, } } From 1c1b5118edc3b0bcc0b7f8e90727df398e902615 Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 08:38:21 +0200 Subject: [PATCH 25/26] Drop TODO about deduplicating exempt paths The paths aren't equal and making a shared list for two entries is excessive. --- pkg/server/filters/perclustercontext.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/server/filters/perclustercontext.go b/pkg/server/filters/perclustercontext.go index 21117b48bab..abe01e60209 100644 --- a/pkg/server/filters/perclustercontext.go +++ b/pkg/server/filters/perclustercontext.go @@ -36,8 +36,6 @@ import ( // This is used e.g. to cancel active connections when a logical cluster // is being migrated. func WithPerClusterContext(handler http.Handler, mgr *contextmanager.Manager[logicalcluster.Path]) http.HandlerFunc { - // TODO(ntnn): THis is the same list as for inactive logical clusters. - // Will look into deduplicating this when implementing lc migration. // exemptPathPrefixes allows some paths to pass without adding a context. exemptPathPrefixes := []string{ // Kube clients expect the /openapi endpoint to be available to From 544e3a0b43a2f5a9243fcacbfc2bd9446a12735b Mon Sep 17 00:00:00 2001 From: "Nelo-T. Wallus" Date: Fri, 5 Jun 2026 10:03:32 +0200 Subject: [PATCH 26/26] make modules --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 1dab076dd6c..4a5a854177b 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,8 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/xrstf/mockoidc v0.0.0-20250721141841-711cc4e835f6 + go.etcd.io/etcd/client/pkg/v3 v3.6.8 + go.etcd.io/etcd/client/v3 v3.6.8 go.uber.org/goleak v1.3.1-0.20251210191316-2b7fd8a0d244 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.1 @@ -179,8 +181,6 @@ require ( github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect go.etcd.io/bbolt v1.4.3 // indirect go.etcd.io/etcd/api/v3 v3.6.8 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.8 // indirect - go.etcd.io/etcd/client/v3 v3.6.8 // indirect go.etcd.io/etcd/pkg/v3 v3.6.8 // indirect go.etcd.io/etcd/server/v3 v3.6.8 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect