mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 14:37:00 +00:00
apiextensions: normalize CR validation to return multiple errors
This commit is contained in:
parent
e59ae29fbc
commit
5d66dc9338
@ -43,73 +43,36 @@ type customResourceValidator struct {
|
||||
}
|
||||
|
||||
func (a customResourceValidator) Validate(ctx context.Context, obj runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), u, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", u))}
|
||||
}
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
typeAccessor, err := meta.TypeAccessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())}
|
||||
}
|
||||
if typeAccessor.GetKind() != a.kind.Kind {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))}
|
||||
}
|
||||
if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))}
|
||||
|
||||
if errs := a.ValidateTypeMeta(ctx, u); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
customResourceObject, ok := obj.(*unstructured.Unstructured)
|
||||
// this will never happen.
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))}
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
|
||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
||||
}
|
||||
customResource := customResourceObject.UnstructuredContent()
|
||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
if err = apiservervalidation.ValidateCustomResource(customResource, a.schemaValidator); err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())}
|
||||
}
|
||||
|
||||
if scale != nil {
|
||||
// validate specReplicas
|
||||
specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".") // ignore leading period
|
||||
specReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(specReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error())}
|
||||
}
|
||||
if specReplicas < 0 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer")}
|
||||
}
|
||||
if specReplicas > math.MaxInt32 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))}
|
||||
}
|
||||
|
||||
// validate statusReplicas
|
||||
statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period
|
||||
statusReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(statusReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error())}
|
||||
}
|
||||
if statusReplicas < 0 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer")}
|
||||
}
|
||||
if statusReplicas > math.MaxInt32 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))}
|
||||
}
|
||||
|
||||
// validate labelSelector
|
||||
if scale.LabelSelectorPath != nil {
|
||||
labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period
|
||||
labelSelector, _, err := unstructured.NestedString(customResource, strings.Split(labelSelectorPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error())}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), u, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", u))}
|
||||
}
|
||||
objAccessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
@ -118,69 +81,28 @@ func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old ru
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
typeAccessor, err := meta.TypeAccessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())}
|
||||
}
|
||||
if typeAccessor.GetKind() != a.kind.Kind {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))}
|
||||
}
|
||||
if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))}
|
||||
|
||||
if errs := a.ValidateTypeMeta(ctx, u); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
customResourceObject, ok := obj.(*unstructured.Unstructured)
|
||||
// this will never happen.
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))}
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
||||
}
|
||||
customResource := customResourceObject.UnstructuredContent()
|
||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
if err = apiservervalidation.ValidateCustomResource(customResource, a.schemaValidator); err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())}
|
||||
}
|
||||
|
||||
if scale != nil {
|
||||
// validate specReplicas
|
||||
specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".") // ignore leading period
|
||||
specReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(specReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error())}
|
||||
}
|
||||
if specReplicas < 0 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer")}
|
||||
}
|
||||
if specReplicas > math.MaxInt32 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))}
|
||||
}
|
||||
|
||||
// validate statusReplicas
|
||||
statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period
|
||||
statusReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(statusReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error())}
|
||||
}
|
||||
if statusReplicas < 0 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer")}
|
||||
}
|
||||
if statusReplicas > math.MaxInt32 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))}
|
||||
}
|
||||
|
||||
// validate labelSelector
|
||||
if scale.LabelSelectorPath != nil {
|
||||
labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period
|
||||
labelSelector, _, err := unstructured.NestedString(customResource, strings.Split(labelSelectorPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error())}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), u, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", u))}
|
||||
}
|
||||
objAccessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
@ -189,53 +111,85 @@ func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj,
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
|
||||
if errs := a.ValidateTypeMeta(ctx, u); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
||||
}
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateTypeMeta(ctx context.Context, obj *unstructured.Unstructured) field.ErrorList {
|
||||
typeAccessor, err := meta.TypeAccessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())}
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
if typeAccessor.GetKind() != a.kind.Kind {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))}
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind)))
|
||||
}
|
||||
if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))}
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version)))
|
||||
}
|
||||
|
||||
customResourceObject, ok := obj.(*unstructured.Unstructured)
|
||||
// this will never happen.
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))}
|
||||
}
|
||||
customResource := customResourceObject.UnstructuredContent()
|
||||
|
||||
// validate only the status
|
||||
customResourceStatus := customResource["status"]
|
||||
if err = apiservervalidation.ValidateCustomResource(customResourceStatus, a.statusSchemaValidator); err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("status"), customResourceStatus, err.Error())}
|
||||
}
|
||||
|
||||
if scale != nil {
|
||||
// validate statusReplicas
|
||||
statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period
|
||||
statusReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(statusReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error())}
|
||||
}
|
||||
if statusReplicas < 0 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer")}
|
||||
}
|
||||
if statusReplicas > math.MaxInt32 {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))}
|
||||
}
|
||||
|
||||
// validate labelSelector
|
||||
if scale.LabelSelectorPath != nil {
|
||||
labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period
|
||||
labelSelector, _, err := unstructured.NestedString(customResource, strings.Split(labelSelectorPath, ".")...)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error())}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateScaleSpec(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
if scale == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
// validate specReplicas
|
||||
specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".") // ignore leading period
|
||||
specReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(specReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error()))
|
||||
} else if specReplicas < 0 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer"))
|
||||
} else if specReplicas > math.MaxInt32 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateScaleStatus(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
if scale == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
// validate statusReplicas
|
||||
statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period
|
||||
statusReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(statusReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error()))
|
||||
} else if statusReplicas < 0 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer"))
|
||||
} else if statusReplicas > math.MaxInt32 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
|
||||
}
|
||||
|
||||
// validate labelSelector
|
||||
if scale.LabelSelectorPath != nil {
|
||||
labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period
|
||||
labelSelector, _, err := unstructured.NestedString(obj.UnstructuredContent(), strings.Split(labelSelectorPath, ".")...)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user