diff --git a/pkg/api/testing/validation_test.go b/pkg/api/testing/validation_test.go index fc2ecf0d97d..dd0bf3ba52e 100644 --- a/pkg/api/testing/validation_test.go +++ b/pkg/api/testing/validation_test.go @@ -39,6 +39,8 @@ func TestVersionedValidationByFuzzing(t *testing.T) { {Group: "resource.k8s.io", Version: "v1beta1"}, {Group: "resource.k8s.io", Version: "v1beta2"}, {Group: "resource.k8s.io", Version: "v1"}, + {Group: "storage.k8s.io", Version: "v1beta1"}, + {Group: "storage.k8s.io", Version: "v1"}, } fuzzIters := *roundtrip.FuzzIters / 10 // TODO: Find a better way to manage test running time diff --git a/pkg/apis/storage/v1/doc.go b/pkg/apis/storage/v1/doc.go index 455fd010a1f..7c35db3b1f2 100644 --- a/pkg/apis/storage/v1/doc.go +++ b/pkg/apis/storage/v1/doc.go @@ -19,5 +19,7 @@ limitations under the License. // +groupName=storage.k8s.io // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/storage/v1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/storage/v1 package v1 diff --git a/pkg/apis/storage/v1/zz_generated.validations.go b/pkg/apis/storage/v1/zz_generated.validations.go new file mode 100644 index 00000000000..0c64a15a042 --- /dev/null +++ b/pkg/apis/storage/v1/zz_generated.validations.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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 validation-gen. DO NOT EDIT. + +package v1 + +import ( + context "context" + fmt "fmt" + + storagev1 "k8s.io/api/storage/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type StorageClass + scheme.AddValidationFunc((*storagev1.StorageClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StorageClass(ctx, op, nil /* fldPath */, obj.(*storagev1.StorageClass), safe.Cast[*storagev1.StorageClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type StorageClassList + scheme.AddValidationFunc((*storagev1.StorageClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StorageClassList(ctx, op, nil /* fldPath */, obj.(*storagev1.StorageClassList), safe.Cast[*storagev1.StorageClassList](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_StorageClass validates an instance of StorageClass according +// to declarative validation rules in the API schema. +func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1.StorageClass) (errs field.ErrorList) { + // field storagev1.StorageClass.TypeMeta has no validation + // field storagev1.StorageClass.ObjectMeta has no validation + + // field storagev1.StorageClass.Provisioner + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("provisioner"), &obj.Provisioner, safe.Field(oldObj, func(oldObj *storagev1.StorageClass) *string { return &oldObj.Provisioner }))...) + + // field storagev1.StorageClass.Parameters has no validation + // field storagev1.StorageClass.ReclaimPolicy has no validation + // field storagev1.StorageClass.MountOptions has no validation + // field storagev1.StorageClass.AllowVolumeExpansion has no validation + // field storagev1.StorageClass.VolumeBindingMode has no validation + // field storagev1.StorageClass.AllowedTopologies has no validation + return errs +} + +// Validate_StorageClassList validates an instance of StorageClassList according +// to declarative validation rules in the API schema. +func Validate_StorageClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1.StorageClassList) (errs field.ErrorList) { + // field storagev1.StorageClassList.TypeMeta has no validation + // field storagev1.StorageClassList.ListMeta has no validation + + // field storagev1.StorageClassList.Items + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []storagev1.StorageClass) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StorageClass)...) + return + }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *storagev1.StorageClassList) []storagev1.StorageClass { return oldObj.Items }))...) + + return errs +} diff --git a/pkg/apis/storage/v1beta1/doc.go b/pkg/apis/storage/v1beta1/doc.go index 60f550386b2..ee186a821dc 100644 --- a/pkg/apis/storage/v1beta1/doc.go +++ b/pkg/apis/storage/v1beta1/doc.go @@ -19,5 +19,7 @@ limitations under the License. // +groupName=storage.k8s.io // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=k8s.io/api/storage/v1beta1 +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-input=k8s.io/api/storage/v1beta1 package v1beta1 diff --git a/pkg/apis/storage/v1beta1/zz_generated.validations.go b/pkg/apis/storage/v1beta1/zz_generated.validations.go new file mode 100644 index 00000000000..21981d88efd --- /dev/null +++ b/pkg/apis/storage/v1beta1/zz_generated.validations.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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 validation-gen. DO NOT EDIT. + +package v1beta1 + +import ( + context "context" + fmt "fmt" + + storagev1beta1 "k8s.io/api/storage/v1beta1" + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + runtime "k8s.io/apimachinery/pkg/runtime" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *runtime.Scheme) error { + // type StorageClass + scheme.AddValidationFunc((*storagev1beta1.StorageClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StorageClass(ctx, op, nil /* fldPath */, obj.(*storagev1beta1.StorageClass), safe.Cast[*storagev1beta1.StorageClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type StorageClassList + scheme.AddValidationFunc((*storagev1beta1.StorageClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StorageClassList(ctx, op, nil /* fldPath */, obj.(*storagev1beta1.StorageClassList), safe.Cast[*storagev1beta1.StorageClassList](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_StorageClass validates an instance of StorageClass according +// to declarative validation rules in the API schema. +func Validate_StorageClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1beta1.StorageClass) (errs field.ErrorList) { + // field storagev1beta1.StorageClass.TypeMeta has no validation + // field storagev1beta1.StorageClass.ObjectMeta has no validation + + // field storagev1beta1.StorageClass.Provisioner + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("provisioner"), &obj.Provisioner, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClass) *string { return &oldObj.Provisioner }))...) + + // field storagev1beta1.StorageClass.Parameters has no validation + // field storagev1beta1.StorageClass.ReclaimPolicy has no validation + // field storagev1beta1.StorageClass.MountOptions has no validation + // field storagev1beta1.StorageClass.AllowVolumeExpansion has no validation + // field storagev1beta1.StorageClass.VolumeBindingMode has no validation + // field storagev1beta1.StorageClass.AllowedTopologies has no validation + return errs +} + +// Validate_StorageClassList validates an instance of StorageClassList according +// to declarative validation rules in the API schema. +func Validate_StorageClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *storagev1beta1.StorageClassList) (errs field.ErrorList) { + // field storagev1beta1.StorageClassList.TypeMeta has no validation + // field storagev1beta1.StorageClassList.ListMeta has no validation + + // field storagev1beta1.StorageClassList.Items + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []storagev1beta1.StorageClass) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the list and call the type's validation function + errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StorageClass)...) + return + }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *storagev1beta1.StorageClassList) []storagev1beta1.StorageClass { return oldObj.Items }))...) + + return errs +} diff --git a/pkg/apis/storage/validation/validation.go b/pkg/apis/storage/validation/validation.go index 4d526815f81..dc9b0193f91 100644 --- a/pkg/apis/storage/validation/validation.go +++ b/pkg/apis/storage/validation/validation.go @@ -84,11 +84,11 @@ func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageCl return allErrs } -// validateProvisioner tests if provisioner is a valid qualified name. +// validateProvisioner tests if provisioner is a validate qualified name. func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(provisioner) == 0 { - allErrs = append(allErrs, field.Required(fldPath, provisioner)) + allErrs = append(allErrs, field.Required(fldPath, provisioner)).MarkCoveredByDeclarative() } if len(provisioner) > 0 { allErrs = append(allErrs, apivalidation.ValidateQualifiedName(strings.ToLower(provisioner), fldPath)...) diff --git a/pkg/registry/storage/storageclass/strategy.go b/pkg/registry/storage/storageclass/strategy.go index 7e833a06f4f..e67dc365505 100644 --- a/pkg/registry/storage/storageclass/strategy.go +++ b/pkg/registry/storage/storageclass/strategy.go @@ -18,6 +18,8 @@ package storageclass import ( "context" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" @@ -48,7 +50,8 @@ func (storageClassStrategy) PrepareForCreate(ctx context.Context, obj runtime.Ob func (storageClassStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { storageClass := obj.(*storage.StorageClass) - return validation.ValidateStorageClass(storageClass) + allErrs := validation.ValidateStorageClass(storageClass) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, nil, allErrs, operation.Create) } // WarningsOnCreate returns warnings for the creation of the given object. @@ -70,7 +73,8 @@ func (storageClassStrategy) PrepareForUpdate(ctx context.Context, obj, old runti func (storageClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { errorList := validation.ValidateStorageClass(obj.(*storage.StorageClass)) - return append(errorList, validation.ValidateStorageClassUpdate(obj.(*storage.StorageClass), old.(*storage.StorageClass))...) + allErrs := append(errorList, validation.ValidateStorageClassUpdate(obj.(*storage.StorageClass), old.(*storage.StorageClass))...) + return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, obj, old, allErrs, operation.Update) } // WarningsOnUpdate returns warnings for the given update. diff --git a/staging/src/k8s.io/api/storage/v1/generated.proto b/staging/src/k8s.io/api/storage/v1/generated.proto index c925f2e192a..d77bea9cc08 100644 --- a/staging/src/k8s.io/api/storage/v1/generated.proto +++ b/staging/src/k8s.io/api/storage/v1/generated.proto @@ -433,6 +433,8 @@ message StorageClass { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // provisioner indicates the type of the provisioner. + // +required + // +k8s:required optional string provisioner = 2; // parameters holds the parameters for the provisioner that should diff --git a/staging/src/k8s.io/api/storage/v1/types.go b/staging/src/k8s.io/api/storage/v1/types.go index 6c56be81a9e..b198cb7138a 100644 --- a/staging/src/k8s.io/api/storage/v1/types.go +++ b/staging/src/k8s.io/api/storage/v1/types.go @@ -41,6 +41,8 @@ type StorageClass struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // provisioner indicates the type of the provisioner. + // +required + // +k8s:required Provisioner string `json:"provisioner" protobuf:"bytes,2,opt,name=provisioner"` // parameters holds the parameters for the provisioner that should diff --git a/staging/src/k8s.io/api/storage/v1beta1/generated.proto b/staging/src/k8s.io/api/storage/v1beta1/generated.proto index da87914f52d..33b904ee6d9 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/storage/v1beta1/generated.proto @@ -435,6 +435,8 @@ message StorageClass { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // provisioner indicates the type of the provisioner. + // +required + // +k8s:required optional string provisioner = 2; // parameters holds the parameters for the provisioner that should diff --git a/staging/src/k8s.io/api/storage/v1beta1/types.go b/staging/src/k8s.io/api/storage/v1beta1/types.go index 1f48ef2103b..b5dde72ad95 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/types.go +++ b/staging/src/k8s.io/api/storage/v1beta1/types.go @@ -43,6 +43,8 @@ type StorageClass struct { metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // provisioner indicates the type of the provisioner. + // +required + // +k8s:required Provisioner string `json:"provisioner" protobuf:"bytes,2,opt,name=provisioner"` // parameters holds the parameters for the provisioner that should