mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-18 16:21:13 +00:00
add variadic options to ValidateUpdate
stays API compatible while allowing us to pass in our CorrelatedObject va
This commit is contained in:
parent
3930f3f834
commit
432e8937cf
@ -54,12 +54,12 @@ func NewRatchetingSchemaValidator(schema *spec.Schema, rootSchema interface{}, r
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RatchetingSchemaValidator) Validate(new interface{}) *validate.Result {
|
||||
func (r *RatchetingSchemaValidator) Validate(new interface{}, options ...ValidationOption) *validate.Result {
|
||||
sv := validate.NewSchemaValidator(r.schema, r.root, r.path, r.knownFormats, r.options...)
|
||||
return sv.Validate(new)
|
||||
}
|
||||
|
||||
func (r *RatchetingSchemaValidator) ValidateUpdate(new, old interface{}) *validate.Result {
|
||||
func (r *RatchetingSchemaValidator) ValidateUpdate(new, old interface{}, options ...ValidationOption) *validate.Result {
|
||||
return newRatchetingValueValidator(
|
||||
common.NewCorrelatedObject(new, old, &celopenapi.Schema{Schema: r.schemaArgs.schema}),
|
||||
r.schemaArgs,
|
||||
|
@ -58,8 +58,8 @@ var largeIntSchema *spec.Schema = &spec.Schema{
|
||||
|
||||
func TestScalarRatcheting(t *testing.T) {
|
||||
validator := validation.NewRatchetingSchemaValidator(mediumIntSchema, nil, "", strfmt.Default)
|
||||
require.True(t, validator.ValidateUpdate(1, 1).IsValid())
|
||||
require.False(t, validator.ValidateUpdate(1, 2).IsValid())
|
||||
require.True(t, validator.ValidateUpdate(1, 1, validation.WithRatcheting(nil)).IsValid())
|
||||
require.False(t, validator.ValidateUpdate(1, 2, validation.WithRatcheting(nil)).IsValid())
|
||||
}
|
||||
|
||||
var objectSchema *spec.Schema = &spec.Schema{
|
||||
@ -90,18 +90,18 @@ func TestObjectScalarFieldsRatcheting(t *testing.T) {
|
||||
"small": 500,
|
||||
}, map[string]interface{}{
|
||||
"small": 500,
|
||||
}).IsValid())
|
||||
}, validation.WithRatcheting(nil)).IsValid())
|
||||
assert.True(t, validator.ValidateUpdate(map[string]interface{}{
|
||||
"small": 501,
|
||||
}, map[string]interface{}{
|
||||
"small": 501,
|
||||
"medium": 500,
|
||||
}).IsValid())
|
||||
}, validation.WithRatcheting(nil)).IsValid())
|
||||
assert.False(t, validator.ValidateUpdate(map[string]interface{}{
|
||||
"small": 500,
|
||||
}, map[string]interface{}{
|
||||
"small": 501,
|
||||
}).IsValid())
|
||||
}, validation.WithRatcheting(nil)).IsValid())
|
||||
}
|
||||
|
||||
// Shows schemas with object fields which themselves are ratcheted can be ratcheted
|
||||
@ -113,7 +113,7 @@ func TestObjectObjectFieldsRatcheting(t *testing.T) {
|
||||
}}, map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"small": 500,
|
||||
}}).IsValid())
|
||||
}}, validation.WithRatcheting(nil)).IsValid())
|
||||
assert.True(t, validator.ValidateUpdate(map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"small": 501,
|
||||
@ -121,14 +121,14 @@ func TestObjectObjectFieldsRatcheting(t *testing.T) {
|
||||
"nested": map[string]interface{}{
|
||||
"small": 501,
|
||||
"medium": 500,
|
||||
}}).IsValid())
|
||||
}}, validation.WithRatcheting(nil)).IsValid())
|
||||
assert.False(t, validator.ValidateUpdate(map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"small": 500,
|
||||
}}, map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"small": 501,
|
||||
}}).IsValid())
|
||||
}}, validation.WithRatcheting(nil)).IsValid())
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T {
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/features"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
openapierrors "k8s.io/kube-openapi/pkg/validation/errors"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
@ -33,22 +34,60 @@ import (
|
||||
|
||||
type SchemaValidator interface {
|
||||
SchemaCreateValidator
|
||||
ValidateUpdate(new, old interface{}) *validate.Result
|
||||
ValidateUpdate(new, old interface{}, options ...ValidationOption) *validate.Result
|
||||
}
|
||||
|
||||
type SchemaCreateValidator interface {
|
||||
Validate(value interface{}) *validate.Result
|
||||
Validate(value interface{}, options ...ValidationOption) *validate.Result
|
||||
}
|
||||
|
||||
type ValidationOptions struct {
|
||||
// Whether errors from unchanged portions of the schema should be ratcheted
|
||||
// This field is ignored for Validate
|
||||
Ratcheting bool
|
||||
|
||||
// Correlation between old and new arguments.
|
||||
// If set, this is expected to be the correlation between the `new` and
|
||||
// `old` arguments to ValidateUpdate, and values for `new` and `old` will
|
||||
// be taken from the correlation.
|
||||
//
|
||||
// This field is ignored for Validate
|
||||
//
|
||||
// Used for ratcheting, but left as a separate field since it may be used
|
||||
// for other purposes in the future.
|
||||
CorrelatedObject *common.CorrelatedObject
|
||||
}
|
||||
|
||||
type ValidationOption func(*ValidationOptions)
|
||||
|
||||
func NewValidationOptions(opts ...ValidationOption) ValidationOptions {
|
||||
options := ValidationOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func WithRatcheting(correlation *common.CorrelatedObject) ValidationOption {
|
||||
return func(options *ValidationOptions) {
|
||||
options.Ratcheting = true
|
||||
options.CorrelatedObject = correlation
|
||||
}
|
||||
}
|
||||
|
||||
// basicSchemaValidator wraps a kube-openapi SchemaCreateValidator to
|
||||
// support ValidateUpdate. It implements ValidateUpdate by simply validating
|
||||
// the new value via kube-openapi, ignoring the old value.
|
||||
// the new value via kube-openapi, ignoring the old value
|
||||
type basicSchemaValidator struct {
|
||||
*validate.SchemaValidator
|
||||
}
|
||||
|
||||
func (s basicSchemaValidator) ValidateUpdate(new, old interface{}) *validate.Result {
|
||||
return s.Validate(new)
|
||||
func (s basicSchemaValidator) Validate(new interface{}, options ...ValidationOption) *validate.Result {
|
||||
return s.SchemaValidator.Validate(new)
|
||||
}
|
||||
|
||||
func (s basicSchemaValidator) ValidateUpdate(new, old interface{}, options ...ValidationOption) *validate.Result {
|
||||
return s.Validate(new, options...)
|
||||
}
|
||||
|
||||
// NewSchemaValidator creates an openapi schema validator for the given CRD validation.
|
||||
@ -80,7 +119,7 @@ func NewSchemaValidator(customResourceValidation *apiextensions.JSONSchemaProps)
|
||||
//
|
||||
// If feature `CRDValidationRatcheting` is disabled, this behaves identically to
|
||||
// ValidateCustomResource(customResource).
|
||||
func ValidateCustomResourceUpdate(fldPath *field.Path, customResource, old interface{}, validator SchemaValidator) field.ErrorList {
|
||||
func ValidateCustomResourceUpdate(fldPath *field.Path, customResource, old interface{}, validator SchemaValidator, options ...ValidationOption) field.ErrorList {
|
||||
// Additional feature gate check for sanity
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.CRDValidationRatcheting) {
|
||||
return ValidateCustomResource(nil, customResource, validator)
|
||||
@ -88,7 +127,7 @@ func ValidateCustomResourceUpdate(fldPath *field.Path, customResource, old inter
|
||||
return nil
|
||||
}
|
||||
|
||||
result := validator.ValidateUpdate(customResource, old)
|
||||
result := validator.ValidateUpdate(customResource, old, options...)
|
||||
if result.IsValid() {
|
||||
return nil
|
||||
}
|
||||
@ -98,12 +137,12 @@ func ValidateCustomResourceUpdate(fldPath *field.Path, customResource, old inter
|
||||
|
||||
// ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition.
|
||||
// CustomResource is a JSON data structure.
|
||||
func ValidateCustomResource(fldPath *field.Path, customResource interface{}, validator SchemaCreateValidator) field.ErrorList {
|
||||
func ValidateCustomResource(fldPath *field.Path, customResource interface{}, validator SchemaCreateValidator, options ...ValidationOption) field.ErrorList {
|
||||
if validator == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := validator.Validate(customResource)
|
||||
result := validator.Validate(customResource, options...)
|
||||
if result.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func (a customResourceValidator) Validate(ctx context.Context, obj *unstructured
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale, options ...apiextensionsvalidation.ValidationOption) field.ErrorList {
|
||||
if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
@ -66,7 +66,7 @@ func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old *u
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
|
||||
allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, obj.UnstructuredContent(), old.UnstructuredContent(), a.schemaValidator)...)
|
||||
allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, obj.UnstructuredContent(), old.UnstructuredContent(), a.schemaValidator, options...)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, obj, scale)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user