mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 07:47:56 +00:00
Return CR validation errors as field errors
This commit is contained in:
parent
eadf68ebd9
commit
4bf7e3f2b1
@ -7,6 +7,7 @@ go 1.12
|
||||
require (
|
||||
github.com/coreos/etcd v3.3.13+incompatible
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible
|
||||
github.com/go-openapi/errors v0.19.2
|
||||
github.com/go-openapi/spec v0.19.2
|
||||
github.com/go-openapi/strfmt v0.19.0
|
||||
github.com/go-openapi/validate v0.19.2
|
||||
|
@ -820,9 +820,8 @@ func (v *specStandardValidatorV3) validate(schema *apiextensions.JSONSchemaProps
|
||||
|
||||
// validate the default value with user the provided schema.
|
||||
validator := govalidate.NewSchemaValidator(s.ToGoOpenAPI(), nil, "", strfmt.Default)
|
||||
if err := apiservervalidation.ValidateCustomResource(interface{}(*schema.Default), validator); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("default"), schema.Default, fmt.Sprintf("must validate: %v", err)))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, apiservervalidation.ValidateCustomResource(fldPath.Child("default"), interface{}(*schema.Default), validator)...)
|
||||
}
|
||||
} else {
|
||||
detail := "must not be set"
|
||||
|
@ -1944,8 +1944,8 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
||||
},
|
||||
errors: []validationMatch{
|
||||
invalid("spec", "validation", "openAPIV3Schema", "properties[a]", "default"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "properties[c]", "default"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "properties[d]", "default"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "properties[c]", "default", "foo"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "properties[d]", "default", "bad"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "properties[d]", "properties[bad]", "pattern"),
|
||||
// we also expected unpruned and valid defaults under x-kubernetes-preserve-unknown-fields. We could be more
|
||||
// strict here, but want to encourage proper specifications by forbidding other defaults.
|
||||
|
@ -13,6 +13,8 @@ go_library(
|
||||
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
|
||||
deps = [
|
||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//vendor/github.com/go-openapi/errors:go_default_library",
|
||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
||||
"//vendor/github.com/go-openapi/strfmt:go_default_library",
|
||||
"//vendor/github.com/go-openapi/validate:go_default_library",
|
||||
@ -45,6 +47,7 @@ go_test(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -17,11 +17,16 @@ limitations under the License.
|
||||
package validation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
openapierrors "github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/validate"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// NewSchemaValidator creates an openapi schema validator for the given CRD validation.
|
||||
@ -39,16 +44,50 @@ func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceVa
|
||||
|
||||
// ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition.
|
||||
// CustomResource is a JSON data structure.
|
||||
func ValidateCustomResource(customResource interface{}, validator *validate.SchemaValidator) error {
|
||||
func ValidateCustomResource(fldPath *field.Path, customResource interface{}, validator *validate.SchemaValidator) field.ErrorList {
|
||||
if validator == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := validator.Validate(customResource)
|
||||
if result.AsError() != nil {
|
||||
return result.AsError()
|
||||
if result.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
var allErrs field.ErrorList
|
||||
for _, err := range result.Errors {
|
||||
switch err := err.(type) {
|
||||
|
||||
case *openapierrors.Validation:
|
||||
switch err.Code() {
|
||||
|
||||
case openapierrors.RequiredFailCode:
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child(strings.TrimPrefix(err.Name, ".")), ""))
|
||||
|
||||
case openapierrors.EnumFailCode:
|
||||
values := []string{}
|
||||
for _, allowedValue := range err.Values {
|
||||
if s, ok := allowedValue.(string); ok {
|
||||
values = append(values, s)
|
||||
} else {
|
||||
allowedJSON, _ := json.Marshal(allowedValue)
|
||||
values = append(values, string(allowedJSON))
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child(strings.TrimPrefix(err.Name, ".")), err.Value, values))
|
||||
|
||||
default:
|
||||
value := interface{}("")
|
||||
if err.Value != nil {
|
||||
value = err.Value
|
||||
}
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child(strings.TrimPrefix(err.Name, ".")), value, err.Error()))
|
||||
}
|
||||
|
||||
default:
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "", err.Error()))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ConvertJSONSchemaProps converts the schema from apiextensions.JSONSchemaPropos to go-openapi/spec.Schema.
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
@ -29,6 +30,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// TestRoundTrip checks the conversion to go-openapi types.
|
||||
@ -121,12 +123,17 @@ func stripIntOrStringType(x interface{}) interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
type failingObject struct {
|
||||
object interface{}
|
||||
expectErrs []string
|
||||
}
|
||||
|
||||
func TestValidateCustomResource(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
schema apiextensions.JSONSchemaProps
|
||||
objects []interface{}
|
||||
failingObjects []interface{}
|
||||
failingObjects []failingObject
|
||||
}{
|
||||
{name: "!nullable",
|
||||
schema: apiextensions.JSONSchemaProps{
|
||||
@ -141,12 +148,13 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
map[string]interface{}{},
|
||||
map[string]interface{}{"field": map[string]interface{}{}},
|
||||
},
|
||||
failingObjects: []interface{}{
|
||||
map[string]interface{}{"field": "foo"},
|
||||
map[string]interface{}{"field": 42},
|
||||
map[string]interface{}{"field": true},
|
||||
map[string]interface{}{"field": 1.2},
|
||||
map[string]interface{}{"field": []interface{}{}},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": "foo"}, expectErrs: []string{`field: Invalid value: "string": field in body must be of type object: "string"`}},
|
||||
{object: map[string]interface{}{"field": 42}, expectErrs: []string{`field: Invalid value: "integer": field in body must be of type object: "integer"`}},
|
||||
{object: map[string]interface{}{"field": true}, expectErrs: []string{`field: Invalid value: "boolean": field in body must be of type object: "boolean"`}},
|
||||
{object: map[string]interface{}{"field": 1.2}, expectErrs: []string{`field: Invalid value: "number": field in body must be of type object: "number"`}},
|
||||
{object: map[string]interface{}{"field": []interface{}{}}, expectErrs: []string{`field: Invalid value: "array": field in body must be of type object: "array"`}},
|
||||
{object: map[string]interface{}{"field": nil}, expectErrs: []string{`field: Invalid value: "null": field in body must be of type object: "null"`}},
|
||||
},
|
||||
},
|
||||
{name: "nullable",
|
||||
@ -163,12 +171,12 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
map[string]interface{}{"field": map[string]interface{}{}},
|
||||
map[string]interface{}{"field": nil},
|
||||
},
|
||||
failingObjects: []interface{}{
|
||||
map[string]interface{}{"field": "foo"},
|
||||
map[string]interface{}{"field": 42},
|
||||
map[string]interface{}{"field": true},
|
||||
map[string]interface{}{"field": 1.2},
|
||||
map[string]interface{}{"field": []interface{}{}},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": "foo"}, expectErrs: []string{`field: Invalid value: "string": field in body must be of type object: "string"`}},
|
||||
{object: map[string]interface{}{"field": 42}, expectErrs: []string{`field: Invalid value: "integer": field in body must be of type object: "integer"`}},
|
||||
{object: map[string]interface{}{"field": true}, expectErrs: []string{`field: Invalid value: "boolean": field in body must be of type object: "boolean"`}},
|
||||
{object: map[string]interface{}{"field": 1.2}, expectErrs: []string{`field: Invalid value: "number": field in body must be of type object: "number"`}},
|
||||
{object: map[string]interface{}{"field": []interface{}{}}, expectErrs: []string{`field: Invalid value: "array": field in body must be of type object: "array"`}},
|
||||
},
|
||||
},
|
||||
{name: "nullable and no type",
|
||||
@ -203,12 +211,12 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
map[string]interface{}{"field": 42},
|
||||
map[string]interface{}{"field": "foo"},
|
||||
},
|
||||
failingObjects: []interface{}{
|
||||
map[string]interface{}{"field": nil},
|
||||
map[string]interface{}{"field": true},
|
||||
map[string]interface{}{"field": 1.2},
|
||||
map[string]interface{}{"field": map[string]interface{}{}},
|
||||
map[string]interface{}{"field": []interface{}{}},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": nil}, expectErrs: []string{`field: Invalid value: "null": field in body must be of type integer,string: "null"`}},
|
||||
{object: map[string]interface{}{"field": true}, expectErrs: []string{`field: Invalid value: "boolean": field in body must be of type integer,string: "boolean"`}},
|
||||
{object: map[string]interface{}{"field": 1.2}, expectErrs: []string{`field: Invalid value: "number": field in body must be of type integer,string: "number"`}},
|
||||
{object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{`field: Invalid value: "object": field in body must be of type integer,string: "object"`}},
|
||||
{object: map[string]interface{}{"field": []interface{}{}}, expectErrs: []string{`field: Invalid value: "array": field in body must be of type integer,string: "array"`}},
|
||||
},
|
||||
},
|
||||
{name: "nullable and x-kubernetes-int-or-string",
|
||||
@ -226,11 +234,11 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
map[string]interface{}{"field": "foo"},
|
||||
map[string]interface{}{"field": nil},
|
||||
},
|
||||
failingObjects: []interface{}{
|
||||
map[string]interface{}{"field": true},
|
||||
map[string]interface{}{"field": 1.2},
|
||||
map[string]interface{}{"field": map[string]interface{}{}},
|
||||
map[string]interface{}{"field": []interface{}{}},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": true}, expectErrs: []string{`field: Invalid value: "boolean": field in body must be of type integer,string: "boolean"`}},
|
||||
{object: map[string]interface{}{"field": 1.2}, expectErrs: []string{`field: Invalid value: "number": field in body must be of type integer,string: "number"`}},
|
||||
{object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{`field: Invalid value: "object": field in body must be of type integer,string: "object"`}},
|
||||
{object: map[string]interface{}{"field": []interface{}{}}, expectErrs: []string{`field: Invalid value: "array": field in body must be of type integer,string: "array"`}},
|
||||
},
|
||||
},
|
||||
{name: "nullable, x-kubernetes-int-or-string and user-provided anyOf",
|
||||
@ -252,11 +260,27 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
map[string]interface{}{"field": 42},
|
||||
map[string]interface{}{"field": "foo"},
|
||||
},
|
||||
failingObjects: []interface{}{
|
||||
map[string]interface{}{"field": true},
|
||||
map[string]interface{}{"field": 1.2},
|
||||
map[string]interface{}{"field": map[string]interface{}{}},
|
||||
map[string]interface{}{"field": []interface{}{}},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": true}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "boolean": field in body must be of type integer,string: "boolean"`,
|
||||
`field: Invalid value: "boolean": field in body must be of type integer: "boolean"`,
|
||||
}},
|
||||
{object: map[string]interface{}{"field": 1.2}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "number": field in body must be of type integer,string: "number"`,
|
||||
`field: Invalid value: "number": field in body must be of type integer: "number"`,
|
||||
}},
|
||||
{object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "object": field in body must be of type integer,string: "object"`,
|
||||
`field: Invalid value: "object": field in body must be of type integer: "object"`,
|
||||
}},
|
||||
{object: map[string]interface{}{"field": []interface{}{}}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "array": field in body must be of type integer,string: "array"`,
|
||||
`field: Invalid value: "array": field in body must be of type integer: "array"`,
|
||||
}},
|
||||
},
|
||||
},
|
||||
{name: "nullable, x-kubernetes-int-or-string and user-provider allOf",
|
||||
@ -282,11 +306,31 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
map[string]interface{}{"field": 42},
|
||||
map[string]interface{}{"field": "foo"},
|
||||
},
|
||||
failingObjects: []interface{}{
|
||||
map[string]interface{}{"field": true},
|
||||
map[string]interface{}{"field": 1.2},
|
||||
map[string]interface{}{"field": map[string]interface{}{}},
|
||||
map[string]interface{}{"field": []interface{}{}},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": true}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate all the schemas (allOf). None validated`,
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "boolean": field in body must be of type integer,string: "boolean"`,
|
||||
`field: Invalid value: "boolean": field in body must be of type integer: "boolean"`,
|
||||
}},
|
||||
{object: map[string]interface{}{"field": 1.2}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate all the schemas (allOf). None validated`,
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "number": field in body must be of type integer,string: "number"`,
|
||||
`field: Invalid value: "number": field in body must be of type integer: "number"`,
|
||||
}},
|
||||
{object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate all the schemas (allOf). None validated`,
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "object": field in body must be of type integer,string: "object"`,
|
||||
`field: Invalid value: "object": field in body must be of type integer: "object"`,
|
||||
}},
|
||||
{object: map[string]interface{}{"field": []interface{}{}}, expectErrs: []string{
|
||||
`: Invalid value: "": "field" must validate all the schemas (allOf). None validated`,
|
||||
`: Invalid value: "": "field" must validate at least one schema (anyOf)`,
|
||||
`field: Invalid value: "array": field in body must be of type integer,string: "array"`,
|
||||
`field: Invalid value: "array": field in body must be of type integer: "array"`,
|
||||
}},
|
||||
},
|
||||
},
|
||||
{name: "invalid regex",
|
||||
@ -298,7 +342,59 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
failingObjects: []interface{}{map[string]interface{}{"field": "foo"}},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": "foo"}, expectErrs: []string{"field: Invalid value: \"\": field in body should match '+, but pattern is invalid: error parsing regexp: missing argument to repetition operator: `+`'"}},
|
||||
},
|
||||
},
|
||||
{name: "required field",
|
||||
schema: apiextensions.JSONSchemaProps{
|
||||
Required: []string{"field"},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"field": {
|
||||
Type: "object",
|
||||
Required: []string{"nested"},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"nested": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"test": "a"}, expectErrs: []string{`field: Required value`}},
|
||||
{object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{`field.nested: Required value`}},
|
||||
},
|
||||
},
|
||||
{name: "enum",
|
||||
schema: apiextensions.JSONSchemaProps{
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"field": {
|
||||
Type: "object",
|
||||
Required: []string{"nestedint", "nestedstring"},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"nestedint": {
|
||||
Type: "integer",
|
||||
Enum: []apiextensions.JSON{1, 2},
|
||||
},
|
||||
"nestedstring": {
|
||||
Type: "string",
|
||||
Enum: []apiextensions.JSON{"a", "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
failingObjects: []failingObject{
|
||||
{object: map[string]interface{}{"field": map[string]interface{}{}}, expectErrs: []string{
|
||||
`field.nestedint: Required value`,
|
||||
`field.nestedstring: Required value`,
|
||||
}},
|
||||
{object: map[string]interface{}{"field": map[string]interface{}{"nestedint": "x", "nestedstring": true}}, expectErrs: []string{
|
||||
`field.nestedint: Invalid value: "string": field.nestedint in body must be of type integer: "string"`,
|
||||
`field.nestedint: Unsupported value: "x": supported values: "1", "2"`,
|
||||
`field.nestedstring: Invalid value: "boolean": field.nestedstring in body must be of type string: "boolean"`,
|
||||
`field.nestedstring: Unsupported value: true: supported values: "a", "b"`,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -308,13 +404,25 @@ func TestValidateCustomResource(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, obj := range tt.objects {
|
||||
if err := ValidateCustomResource(obj, validator); err != nil {
|
||||
t.Errorf("unexpected validation error for %v: %v", obj, err)
|
||||
if errs := ValidateCustomResource(nil, obj, validator); len(errs) > 0 {
|
||||
t.Errorf("unexpected validation error for %v: %v", obj, errs)
|
||||
}
|
||||
}
|
||||
for _, obj := range tt.failingObjects {
|
||||
if err := ValidateCustomResource(obj, validator); err == nil {
|
||||
t.Errorf("missing error for %v", obj)
|
||||
for i, failingObject := range tt.failingObjects {
|
||||
if errs := ValidateCustomResource(nil, failingObject.object, validator); len(errs) == 0 {
|
||||
t.Errorf("missing error for %v", failingObject.object)
|
||||
} else {
|
||||
sawErrors := sets.NewString()
|
||||
for _, err := range errs {
|
||||
sawErrors.Insert(err.Error())
|
||||
}
|
||||
expectErrs := sets.NewString(failingObject.expectErrs...)
|
||||
for _, unexpectedError := range sawErrors.Difference(expectErrs).List() {
|
||||
t.Errorf("%d: unexpected error: %s", i, unexpectedError)
|
||||
}
|
||||
for _, missingError := range expectErrs.Difference(sawErrors).List() {
|
||||
t.Errorf("%d: missing error: %s", i, missingError)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -367,11 +475,11 @@ func TestItemsProperty(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ValidateCustomResource(tt.args.object, validator); (err != nil) != tt.wantErr {
|
||||
if err == nil {
|
||||
if errs := ValidateCustomResource(nil, tt.args.object, validator); (len(errs) > 0) != tt.wantErr {
|
||||
if len(errs) == 0 {
|
||||
t.Error("expected error, but didn't get one")
|
||||
} else {
|
||||
t.Errorf("unexpected validation error: %v", err)
|
||||
t.Errorf("unexpected validation error: %v", errs)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -59,9 +59,7 @@ func (a customResourceValidator) Validate(ctx context.Context, obj runtime.Objec
|
||||
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()))
|
||||
}
|
||||
allErrs = append(allErrs, apiservervalidation.ValidateCustomResource(nil, u.UnstructuredContent(), a.schemaValidator)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
@ -89,9 +87,7 @@ func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old ru
|
||||
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, apiservervalidation.ValidateCustomResource(nil, u.UnstructuredContent(), a.schemaValidator)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
@ -119,9 +115,7 @@ func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj,
|
||||
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, apiservervalidation.ValidateCustomResource(nil, u.UnstructuredContent(), a.schemaValidator)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
return allErrs
|
||||
|
@ -321,9 +321,9 @@ func TestCustomResourceValidationErrors(t *testing.T) {
|
||||
ns := "not-the-default"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
instanceFn func() *unstructured.Unstructured
|
||||
expectedError string
|
||||
name string
|
||||
instanceFn func() *unstructured.Unstructured
|
||||
expectedErrors []string
|
||||
}{
|
||||
{
|
||||
name: "bad alpha",
|
||||
@ -332,7 +332,7 @@ func TestCustomResourceValidationErrors(t *testing.T) {
|
||||
instance.Object["alpha"] = "foo_123!"
|
||||
return instance
|
||||
},
|
||||
expectedError: "alpha in body should match '^[a-zA-Z0-9_]*$'",
|
||||
expectedErrors: []string{"alpha in body should match '^[a-zA-Z0-9_]*$'"},
|
||||
},
|
||||
{
|
||||
name: "bad beta",
|
||||
@ -341,7 +341,7 @@ func TestCustomResourceValidationErrors(t *testing.T) {
|
||||
instance.Object["beta"] = 5
|
||||
return instance
|
||||
},
|
||||
expectedError: "beta in body should be greater than or equal to 10",
|
||||
expectedErrors: []string{"beta in body should be greater than or equal to 10"},
|
||||
},
|
||||
{
|
||||
name: "bad gamma",
|
||||
@ -350,7 +350,7 @@ func TestCustomResourceValidationErrors(t *testing.T) {
|
||||
instance.Object["gamma"] = "qux"
|
||||
return instance
|
||||
},
|
||||
expectedError: "gamma in body should be one of [foo bar baz]",
|
||||
expectedErrors: []string{`gamma: Unsupported value: "qux": supported values: "foo", "bar", "baz"`},
|
||||
},
|
||||
{
|
||||
name: "bad delta",
|
||||
@ -359,7 +359,10 @@ func TestCustomResourceValidationErrors(t *testing.T) {
|
||||
instance.Object["delta"] = "foobarbaz"
|
||||
return instance
|
||||
},
|
||||
expectedError: "must validate at least one schema (anyOf)\ndelta in body should be at most 5 chars long",
|
||||
expectedErrors: []string{
|
||||
"must validate at least one schema (anyOf)",
|
||||
"delta in body should be at most 5 chars long",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absent alpha and beta",
|
||||
@ -377,7 +380,7 @@ func TestCustomResourceValidationErrors(t *testing.T) {
|
||||
}
|
||||
return instance
|
||||
},
|
||||
expectedError: ".alpha in body is required\n.beta in body is required",
|
||||
expectedErrors: []string{"alpha: Required value", "beta: Required value"},
|
||||
},
|
||||
}
|
||||
|
||||
@ -388,13 +391,14 @@ func TestCustomResourceValidationErrors(t *testing.T) {
|
||||
instanceToCreate.Object["apiVersion"] = fmt.Sprintf("%s/%s", noxuDefinition.Spec.Group, v.Name)
|
||||
_, err := noxuResourceClient.Create(instanceToCreate, metav1.CreateOptions{})
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected %v", tc.name, tc.expectedError)
|
||||
t.Errorf("%v: expected %v", tc.name, tc.expectedErrors)
|
||||
continue
|
||||
}
|
||||
// this only works when status errors contain the expect kind and version, so this effectively tests serializations too
|
||||
if !strings.Contains(err.Error(), tc.expectedError) {
|
||||
t.Errorf("%v: expected %v, got %v", tc.name, tc.expectedError, err)
|
||||
continue
|
||||
for _, expectedError := range tc.expectedErrors {
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("%v: expected %v, got %v", tc.name, expectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user