migrate StorageClass.ReclaimPolicy to declarative validation

This commit is contained in:
pranshul gupta
2026-02-07 20:35:21 +05:30
parent c1f538118d
commit 5cd3d3f2d7
6 changed files with 60 additions and 5 deletions

View File

@@ -25,6 +25,7 @@ import (
context "context"
fmt "fmt"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
@@ -106,7 +107,27 @@ func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath
return
}(fldPath.Child("parameters"), obj.Parameters, safe.Field(oldObj, func(oldObj *storagev1.StorageClass) map[string]string { return oldObj.Parameters }), oldObj != nil)...)
// field storagev1.StorageClass.ReclaimPolicy has no validation
// field storagev1.StorageClass.ReclaimPolicy
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *corev1.PersistentVolumeReclaimPolicy, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("reclaimPolicy"), obj.ReclaimPolicy, safe.Field(oldObj, func(oldObj *storagev1.StorageClass) *corev1.PersistentVolumeReclaimPolicy {
return oldObj.ReclaimPolicy
}), oldObj != nil)...)
// field storagev1.StorageClass.MountOptions has no validation
// field storagev1.StorageClass.AllowVolumeExpansion has no validation
// field storagev1.StorageClass.VolumeBindingMode has no validation

View File

@@ -25,6 +25,7 @@ import (
context "context"
fmt "fmt"
v1 "k8s.io/api/core/v1"
storagev1beta1 "k8s.io/api/storage/v1beta1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
@@ -106,7 +107,27 @@ func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath
return
}(fldPath.Child("parameters"), obj.Parameters, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClass) map[string]string { return oldObj.Parameters }), oldObj != nil)...)
// field storagev1beta1.StorageClass.ReclaimPolicy has no validation
// field storagev1beta1.StorageClass.ReclaimPolicy
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *v1.PersistentVolumeReclaimPolicy, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("reclaimPolicy"), obj.ReclaimPolicy, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClass) *v1.PersistentVolumeReclaimPolicy {
return oldObj.ReclaimPolicy
}), oldObj != nil)...)
// field storagev1beta1.StorageClass.MountOptions has no validation
// field storagev1beta1.StorageClass.AllowVolumeExpansion has no validation
// field storagev1beta1.StorageClass.VolumeBindingMode has no validation

View File

@@ -66,9 +66,7 @@ func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageCl
allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.Provisioner, oldStorageClass.Provisioner, field.NewPath("provisioner")).WithOrigin("immutable").MarkCoveredByDeclarative()...)
if *storageClass.ReclaimPolicy != *oldStorageClass.ReclaimPolicy {
allErrs = append(allErrs, field.Forbidden(field.NewPath("reclaimPolicy"), "updates to reclaimPolicy are forbidden."))
}
allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.ReclaimPolicy, oldStorageClass.ReclaimPolicy, field.NewPath("reclaimPolicy")).WithOrigin("immutable").MarkCoveredByDeclarative()...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
return allErrs

View File

@@ -102,6 +102,13 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
field.Invalid(field.NewPath("parameters"), map[string]string{"new": "value"}, "field is immutable").WithOrigin("immutable"),
},
},
"invalid update reclaimPolicy changed": {
oldObj: mkValidStorageClass(),
updateObj: mkValidStorageClass(TweakReclaimPolicy(api.PersistentVolumeReclaimRetain)),
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("reclaimPolicy"), api.PersistentVolumeReclaimRetain, "field is immutable").WithOrigin("immutable"),
},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
@@ -149,3 +156,9 @@ func TweakParameters(parameters map[string]string) func(obj *storage.StorageClas
obj.Parameters = parameters
}
}
func TweakReclaimPolicy(reclaimPolicy api.PersistentVolumeReclaimPolicy) func(obj *storage.StorageClass) {
return func(obj *storage.StorageClass) {
obj.ReclaimPolicy = &reclaimPolicy
}
}

View File

@@ -55,6 +55,7 @@ type StorageClass struct {
// reclaimPolicy controls the reclaimPolicy for dynamically provisioned PersistentVolumes of this storage class.
// Defaults to Delete.
// +optional
// +k8s:immutable
ReclaimPolicy *v1.PersistentVolumeReclaimPolicy `json:"reclaimPolicy,omitempty" protobuf:"bytes,4,opt,name=reclaimPolicy,casttype=k8s.io/api/core/v1.PersistentVolumeReclaimPolicy"`
// mountOptions controls the mountOptions for dynamically provisioned PersistentVolumes of this storage class.

View File

@@ -57,6 +57,7 @@ type StorageClass struct {
// reclaimPolicy controls the reclaimPolicy for dynamically provisioned PersistentVolumes of this storage class.
// Defaults to Delete.
// +optional
// +k8s:immutable
ReclaimPolicy *v1.PersistentVolumeReclaimPolicy `json:"reclaimPolicy,omitempty" protobuf:"bytes,4,opt,name=reclaimPolicy,casttype=k8s.io/api/core/v1.PersistentVolumeReclaimPolicy"`
// mountOptions controls the mountOptions for dynamically provisioned PersistentVolumes of this storage class.