add webhook for validation backupClass is immutable in BackupJob

Signed-off-by: Andrey Kolkov <androndo@gmail.com>
This commit is contained in:
Andrey Kolkov 2026-01-22 12:30:41 +04:00
parent f6641c1547
commit 2cca1bc8d8
3 changed files with 79 additions and 0 deletions

View file

@ -52,6 +52,8 @@ type BackupJobSpec struct {
// BackupClassName references a BackupClass that contains strategy and storage configuration.
// The BackupClass will be resolved to determine the appropriate strategy and storage
// based on the ApplicationRef.
// This field is immutable once the BackupJob is created.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="backupClassName is immutable"
BackupClassName string `json:"backupClassName"`
}

View file

@ -0,0 +1,71 @@
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// SetupWebhookWithManager registers the BackupJob webhook with the manager.
func SetupBackupJobWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(&BackupJob{}).
Complete()
}
// +kubebuilder:webhook:path=/mutate-backups-cozystack-io-v1alpha1-backupjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=mbackupjob.kb.io,admissionReviewVersions=v1
var _ webhook.Defaulter = &BackupJob{}
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (j *BackupJob) Default() {
// No defaults needed for BackupJob currently
}
// +kubebuilder:webhook:path=/validate-backups-cozystack-io-v1alpha1-backupjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=vbackupjob.kb.io,admissionReviewVersions=v1
var _ webhook.Validator = &BackupJob{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (j *BackupJob) ValidateCreate() (admission.Warnings, error) {
logger := log.FromContext(context.Background())
logger.Info("validating BackupJob creation", "name", j.Name, "namespace", j.Namespace)
// Validate that backupClassName is set
if j.Spec.BackupClassName == "" {
return nil, fmt.Errorf("backupClassName is required and cannot be empty")
}
return nil, nil
}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (j *BackupJob) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
logger := log.FromContext(context.Background())
logger.Info("validating BackupJob update", "name", j.Name, "namespace", j.Namespace)
oldJob, ok := old.(*BackupJob)
if !ok {
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a BackupJob but got a %T", old))
}
// Enforce immutability of backupClassName
if oldJob.Spec.BackupClassName != "" && oldJob.Spec.BackupClassName != j.Spec.BackupClassName {
return nil, fmt.Errorf("backupClassName is immutable and cannot be changed from %q to %q", oldJob.Spec.BackupClassName, j.Spec.BackupClassName)
}
return nil, nil
}
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (j *BackupJob) ValidateDelete() (admission.Warnings, error) {
// No validation needed for deletion
return nil, nil
}

View file

@ -168,6 +168,12 @@ func main() {
os.Exit(1)
}
// Register BackupJob webhook for validation (immutability of backupClassName)
if err = backupsv1alpha1.SetupBackupJobWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "BackupJob")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {