From 4f55d9778098da587ab21c93c60ce4a2b350d753 Mon Sep 17 00:00:00 2001 From: Christian Ang Date: Tue, 9 May 2023 21:59:47 +0000 Subject: [PATCH] Add skip validate delete webhook annotation - this is necessary for clusterctl move to work because it attempts to delete everything off the source cluster and it cannot control the order of deletion so it may delete pools before ipaddressclaims Co-authored-by: Edwin Xie --- internal/webhooks/inclusterippool.go | 11 +++ internal/webhooks/inclusterippool_test.go | 83 +++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/internal/webhooks/inclusterippool.go b/internal/webhooks/inclusterippool.go index 86342c9..e21d88e 100644 --- a/internal/webhooks/inclusterippool.go +++ b/internal/webhooks/inclusterippool.go @@ -20,6 +20,13 @@ import ( "github.com/telekom/cluster-api-ipam-provider-in-cluster/pkg/types" ) +const ( + // SkipValidateDeleteWebhookAnnotation is an annotation that can be applied + // to the InClusterIPPool or GlobalInClusterIPPool to skip delete + // validation. Necessary for clusterctl move to work as expected. + SkipValidateDeleteWebhookAnnotation = "ipam.cluster.x-k8s.io/skip-validate-delete-webhook" +) + func (webhook *InClusterIPPool) SetupWebhookWithManager(mgr ctrl.Manager) error { err := ctrl.NewWebhookManagedBy(mgr). For(&v1alpha1.InClusterIPPool{}). @@ -129,6 +136,10 @@ func (webhook *InClusterIPPool) ValidateDelete(ctx context.Context, obj runtime. return apierrors.NewBadRequest(fmt.Sprintf("expected a InClusterIPPool or an GlobalInClusterIPPool but got a %T", obj)) } + if _, ok := pool.GetAnnotations()[SkipValidateDeleteWebhookAnnotation]; ok { + return nil + } + poolTypeRef := corev1.TypedLocalObjectReference{ APIGroup: pointer.String(pool.GetObjectKind().GroupVersionKind().Group), Kind: pool.GetObjectKind().GroupVersionKind().Kind, diff --git a/internal/webhooks/inclusterippool_test.go b/internal/webhooks/inclusterippool_test.go index d9c0639..68d5ade 100644 --- a/internal/webhooks/inclusterippool_test.go +++ b/internal/webhooks/inclusterippool_test.go @@ -100,6 +100,89 @@ func TestPoolDeletionWithExistingIPAddresses(t *testing.T) { g.Expect(webhook.ValidateDelete(ctx, globalPool)).To(Succeed(), "should allow deletion when no claims exist") } +func TestDeleteSkip(t *testing.T) { + g := NewWithT(t) + + scheme := runtime.NewScheme() + g.Expect(ipamv1.AddToScheme(scheme)).To(Succeed()) + + namespacedPool := &v1alpha1.InClusterIPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pool", + Annotations: map[string]string{ + SkipValidateDeleteWebhookAnnotation: "", + }, + }, + Spec: v1alpha1.InClusterIPPoolSpec{ + Addresses: []string{"10.0.0.10-10.0.0.20"}, + Prefix: 24, + Gateway: "10.0.0.1", + }, + } + + globalPool := &v1alpha1.InClusterIPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pool", + Annotations: map[string]string{ + SkipValidateDeleteWebhookAnnotation: "", + }, + }, + Spec: v1alpha1.InClusterIPPoolSpec{ + Addresses: []string{"10.0.0.10-10.0.0.20"}, + Prefix: 24, + Gateway: "10.0.0.1", + }, + } + + ips := []client.Object{ + &ipamv1.IPAddress{ + TypeMeta: metav1.TypeMeta{ + Kind: "IPAddress", + APIVersion: "ipam.cluster.x-k8s.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ip", + }, + Spec: ipamv1.IPAddressSpec{ + PoolRef: corev1.TypedLocalObjectReference{ + APIGroup: pointer.String(namespacedPool.GetObjectKind().GroupVersionKind().Group), + Kind: namespacedPool.GetObjectKind().GroupVersionKind().Kind, + Name: namespacedPool.GetName(), + }, + }, + }, + &ipamv1.IPAddress{ + TypeMeta: metav1.TypeMeta{ + Kind: "IPAddress", + APIVersion: "ipam.cluster.x-k8s.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ip-2", + }, + Spec: ipamv1.IPAddressSpec{ + PoolRef: corev1.TypedLocalObjectReference{ + APIGroup: pointer.String(globalPool.GetObjectKind().GroupVersionKind().Group), + Kind: globalPool.GetObjectKind().GroupVersionKind().Kind, + Name: globalPool.GetName(), + }, + }, + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(ips...). + WithIndex(&ipamv1.IPAddress{}, index.IPAddressPoolRefCombinedField, index.IPAddressByCombinedPoolRef). + Build() + + webhook := InClusterIPPool{ + Client: fakeClient, + } + + g.Expect(webhook.ValidateDelete(ctx, namespacedPool)).To(Succeed()) + g.Expect(webhook.ValidateDelete(ctx, globalPool)).To(Succeed()) +} + func TestInClusterIPPoolDefaulting(t *testing.T) { g := NewWithT(t)