From dea030d51573a9f68995e1ec0146e5bbac394c53 Mon Sep 17 00:00:00 2001 From: Xieql Date: Mon, 23 Oct 2023 17:03:27 +0800 Subject: [PATCH] backup: init restore webhook Signed-off-by: Xieql --- pkg/webhooks/restore_webhook.go | 65 ++++++++++++++++++ pkg/webhooks/restore_webhook_test.go | 68 +++++++++++++++++++ .../restore/invalid-destination-cluster.yaml | 9 +++ .../restore/invalid-resource-filter.yaml | 12 ++++ 4 files changed, 154 insertions(+) create mode 100644 pkg/webhooks/restore_webhook.go create mode 100644 pkg/webhooks/restore_webhook_test.go create mode 100644 pkg/webhooks/testdata/restore/invalid-destination-cluster.yaml create mode 100644 pkg/webhooks/testdata/restore/invalid-resource-filter.yaml diff --git a/pkg/webhooks/restore_webhook.go b/pkg/webhooks/restore_webhook.go new file mode 100644 index 000000000..05588f359 --- /dev/null +++ b/pkg/webhooks/restore_webhook.go @@ -0,0 +1,65 @@ +package webhooks + +import ( + "context" + "fmt" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "kurator.dev/kurator/pkg/apis/backups/v1alpha1" + "kurator.dev/kurator/pkg/webhooks/validation" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +var _ webhook.CustomValidator = &RestoreWebhook{} + +type RestoreWebhook struct { + Client client.Reader +} + +func (wh *RestoreWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(&v1alpha1.Restore{}). + WithValidator(wh). + Complete() +} + +func (wh *RestoreWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) error { + in, ok := obj.(*v1alpha1.Restore) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("expected a Restore but got a %T", obj)) + } + + return wh.validate(in) +} + +func (wh *RestoreWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error { + in, ok := newObj.(*v1alpha1.Restore) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("expected a Restore but got a %T", newObj)) + } + + return wh.validate(in) +} + +func (wh *RestoreWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) error { + return nil +} + +func (wh *RestoreWebhook) validate(in *v1alpha1.Restore) error { + var allErrs field.ErrorList + + // Validate referenced clusters in destination + allErrs = append(allErrs, validation.ValidateDestinationClusters(in.Spec.Destination.Clusters)...) + + // Validate Resource Filter + allErrs = append(allErrs, validation.ValidateResourceFilter(in.Spec.Policy.ResourceFilter)...) + + if len(allErrs) > 0 { + return apierrors.NewInvalid(v1alpha1.SchemeGroupVersion.WithKind("Restore").GroupKind(), in.Name, allErrs) + } + + return nil +} diff --git a/pkg/webhooks/restore_webhook_test.go b/pkg/webhooks/restore_webhook_test.go new file mode 100644 index 000000000..a6c42066a --- /dev/null +++ b/pkg/webhooks/restore_webhook_test.go @@ -0,0 +1,68 @@ +/* +Copyright Kurator 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 webhooks + +import ( + "io/fs" + "os" + "path" + "path/filepath" + "testing" + + . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/yaml" + + "kurator.dev/kurator/pkg/apis/backups/v1alpha1" +) + +func TestInvalidRestoreValidation(t *testing.T) { + r := path.Join("testdata", "restore") + caseNames := make([]string, 0) + err := filepath.WalkDir(r, func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + caseNames = append(caseNames, path) + return nil + }) + assert.NoError(t, err) + + wh := &RestoreWebhook{} + for _, tt := range caseNames { + t.Run(tt, func(t *testing.T) { + g := NewWithT(t) + c, err := readRestore(tt) + g.Expect(err).NotTo(HaveOccurred()) + + err = wh.validate(c) + g.Expect(err).To(HaveOccurred()) + t.Logf("%v", err) + }) + } +} + +func readRestore(filename string) (*v1alpha1.Restore, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + + c := &v1alpha1.Restore{} + if err := yaml.Unmarshal(b, c); err != nil { + return nil, err + } + + return c, nil +} diff --git a/pkg/webhooks/testdata/restore/invalid-destination-cluster.yaml b/pkg/webhooks/testdata/restore/invalid-destination-cluster.yaml new file mode 100644 index 000000000..99adf9cea --- /dev/null +++ b/pkg/webhooks/testdata/restore/invalid-destination-cluster.yaml @@ -0,0 +1,9 @@ +apiVersion: backups.kurator.dev/v1alpha1 +kind: Restore +metadata: + name: missing-cluster-name-restore +spec: + backupName: test-backup + destination: + clusters: + - name: kurator-member1 diff --git a/pkg/webhooks/testdata/restore/invalid-resource-filter.yaml b/pkg/webhooks/testdata/restore/invalid-resource-filter.yaml new file mode 100644 index 000000000..6c0f57767 --- /dev/null +++ b/pkg/webhooks/testdata/restore/invalid-resource-filter.yaml @@ -0,0 +1,12 @@ +apiVersion: backups.kurator.dev/v1alpha1 +kind: Restore +metadata: + name: conflicting-namespaces-restore +spec: + backupName: test-backup + policy: + resourceFilter: + includedNamespaces: + - "namespace1" + excludedNamespaces: + - "namespace1"