mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
apiextions: add list-type: map|set CR validation
This commit is contained in:
parent
e6c4ac3f0b
commit
ea45da7e52
@ -36,6 +36,7 @@ filegroup(
|
|||||||
srcs = [
|
srcs = [
|
||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting:all-srcs",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting:all-srcs",
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype:all-srcs",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta:all-srcs",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta:all-srcs",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning:all-srcs",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning:all-srcs",
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["validation.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype",
|
||||||
|
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["validation_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package listtype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateListSetsAndMaps validates that arrays with x-kubernetes-list-type "map" and "set" fulfill the uniqueness
|
||||||
|
// invariants for the keys (maps) and whole elements (sets).
|
||||||
|
func ValidateListSetsAndMaps(fldPath *field.Path, s *schema.Structural, obj map[string]interface{}) field.ErrorList {
|
||||||
|
if s == nil || obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs field.ErrorList
|
||||||
|
|
||||||
|
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
|
||||||
|
for k, v := range obj {
|
||||||
|
errs = append(errs, validationListSetAndMaps(fldPath.Key(k), s.AdditionalProperties.Structural, v)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.Properties != nil {
|
||||||
|
for k, v := range obj {
|
||||||
|
if sub, ok := s.Properties[k]; ok {
|
||||||
|
errs = append(errs, validationListSetAndMaps(fldPath.Child(k), &sub, v)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validationListSetAndMaps(fldPath *field.Path, s *schema.Structural, obj interface{}) field.ErrorList {
|
||||||
|
switch obj := obj.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
return validateListSetsAndMapsArray(fldPath, s, obj)
|
||||||
|
case map[string]interface{}:
|
||||||
|
return ValidateListSetsAndMaps(fldPath, s, obj)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateListSetsAndMapsArray(fldPath *field.Path, s *schema.Structural, obj []interface{}) field.ErrorList {
|
||||||
|
var errs field.ErrorList
|
||||||
|
|
||||||
|
if s.XListType != nil {
|
||||||
|
switch *s.XListType {
|
||||||
|
case "set":
|
||||||
|
nonUnique, err := validateListSet(fldPath, obj)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
} else {
|
||||||
|
for _, i := range nonUnique {
|
||||||
|
errs = append(errs, field.Duplicate(fldPath.Index(i), obj[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "map":
|
||||||
|
errs = append(errs, validateListMap(fldPath, s, obj)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Items != nil {
|
||||||
|
for i := range obj {
|
||||||
|
errs = append(errs, validationListSetAndMaps(fldPath.Index(i), s.Items, obj[i])...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateListSet validated uniqueness of unstructured objects (scalar and compound) and
|
||||||
|
// returns the first non-unique appearance of items.
|
||||||
|
//
|
||||||
|
// As a special case to distinguish undefined key and null values, we allow unspecifiedKeyValue and nullObjectValue
|
||||||
|
// which are both handled like scalars with correct comparison by Golang.
|
||||||
|
func validateListSet(fldPath *field.Path, obj []interface{}) ([]int, *field.Error) {
|
||||||
|
if len(obj) <= 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seenScalars := make(map[interface{}]int, len(obj))
|
||||||
|
seenCompounds := make(map[string]int, len(obj))
|
||||||
|
var nonUniqueIndices []int
|
||||||
|
for i, x := range obj {
|
||||||
|
switch x.(type) {
|
||||||
|
case map[string]interface{}, []interface{}:
|
||||||
|
bs, err := json.Marshal(x)
|
||||||
|
if err != nil {
|
||||||
|
return nil, field.Invalid(fldPath.Index(i), x, "internal error")
|
||||||
|
}
|
||||||
|
s := string(bs)
|
||||||
|
if times, seen := seenCompounds[s]; !seen {
|
||||||
|
seenCompounds[s] = 1
|
||||||
|
} else {
|
||||||
|
seenCompounds[s]++
|
||||||
|
if times == 1 {
|
||||||
|
nonUniqueIndices = append(nonUniqueIndices, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if times, seen := seenScalars[x]; !seen {
|
||||||
|
seenScalars[x] = 1
|
||||||
|
} else {
|
||||||
|
seenScalars[x]++
|
||||||
|
if times == 1 {
|
||||||
|
nonUniqueIndices = append(nonUniqueIndices, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonUniqueIndices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateListMap(fldPath *field.Path, s *schema.Structural, obj []interface{}) field.ErrorList {
|
||||||
|
// only allow nil and objects
|
||||||
|
for i, x := range obj {
|
||||||
|
if _, ok := x.(map[string]interface{}); x != nil && !ok {
|
||||||
|
return field.ErrorList{field.Invalid(fldPath.Index(i), x, "must be an object for an array of list-type map")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj) <= 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// optimize simple case of one key
|
||||||
|
if len(s.XListMapKeys) == 1 {
|
||||||
|
type unspecifiedKeyValue struct{}
|
||||||
|
|
||||||
|
keyField := s.XListMapKeys[0]
|
||||||
|
keys := make([]interface{}, 0, len(obj))
|
||||||
|
for _, x := range obj {
|
||||||
|
if x == nil {
|
||||||
|
keys = append(keys, unspecifiedKeyValue{}) // nil object means unspecified key
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
x := x.(map[string]interface{})
|
||||||
|
|
||||||
|
// undefined key?
|
||||||
|
key, ok := x[keyField]
|
||||||
|
if !ok {
|
||||||
|
keys = append(keys, unspecifiedKeyValue{})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonUnique, err := validateListSet(fldPath, keys)
|
||||||
|
if err != nil {
|
||||||
|
return field.ErrorList{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs field.ErrorList
|
||||||
|
for _, i := range nonUnique {
|
||||||
|
switch keys[i] {
|
||||||
|
case unspecifiedKeyValue{}:
|
||||||
|
errs = append(errs, field.Duplicate(fldPath.Index(i), map[string]interface{}{}))
|
||||||
|
default:
|
||||||
|
errs = append(errs, field.Duplicate(fldPath.Index(i), map[string]interface{}{keyField: keys[i]}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiple key fields
|
||||||
|
keys := make([]interface{}, 0, len(obj))
|
||||||
|
for _, x := range obj {
|
||||||
|
key := map[string]interface{}{}
|
||||||
|
if x == nil {
|
||||||
|
keys = append(keys, key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
x := x.(map[string]interface{})
|
||||||
|
|
||||||
|
for _, keyField := range s.XListMapKeys {
|
||||||
|
if k, ok := x[keyField]; ok {
|
||||||
|
key[keyField] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonUnique, err := validateListSet(fldPath, keys)
|
||||||
|
if err != nil {
|
||||||
|
return field.ErrorList{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs field.ErrorList
|
||||||
|
for _, i := range nonUnique {
|
||||||
|
errs = append(errs, field.Duplicate(fldPath.Index(i), keys[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
@ -0,0 +1,940 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package listtype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateListSetsAndMaps(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
schema *schema.Structural
|
||||||
|
obj map[string]interface{}
|
||||||
|
errors []validationMatch
|
||||||
|
}{
|
||||||
|
{name: "nil"},
|
||||||
|
{name: "no schema", obj: make(map[string]interface{})},
|
||||||
|
{name: "no object", schema: &schema.Structural{}},
|
||||||
|
{name: "list without schema",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "b", "a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "list without items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "b", "a"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "set list with one item",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "set list with two equal items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "a"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root", "array[1]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "set list with two different items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "b"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "set list with multiple duplicated items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "a", "b", "c", "d", "c"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root", "array[1]"),
|
||||||
|
duplicate("root", "array[5]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "normal list with items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "b", "a"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "set list with items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "b", "a"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root", "array[2]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "set list with items under additionalProperties",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{"a", "b", "a"},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root[array][2]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "set list with items under items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{
|
||||||
|
[]interface{}{"a", "b", "a"},
|
||||||
|
[]interface{}{"b", "b", "a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root", "array[0][2]"),
|
||||||
|
duplicate("root", "array[1][1]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "nested set lists",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{
|
||||||
|
"a", "b", "a", []interface{}{"b", "b", "a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root", "array[2]"),
|
||||||
|
duplicate("root", "array[3][1]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "set list with compound map items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"strings": []interface{}{"a", "b", "a"},
|
||||||
|
"integers": []interface{}{int64(1), int64(2), int64(1)},
|
||||||
|
"booleans": []interface{}{false, true, true},
|
||||||
|
"float64": []interface{}{float64(1.0), float64(2.0), float64(2.0)},
|
||||||
|
"nil": []interface{}{"a", nil, nil},
|
||||||
|
"empty maps": []interface{}{map[string]interface{}{"a": "b"}, map[string]interface{}{}, map[string]interface{}{}},
|
||||||
|
"map values": []interface{}{map[string]interface{}{"a": "b"}, map[string]interface{}{"a": "c"}, map[string]interface{}{"a": "b"}},
|
||||||
|
"nil values": []interface{}{map[string]interface{}{"a": nil}, map[string]interface{}{"b": "c", "a": nil}},
|
||||||
|
"array": []interface{}{[]interface{}{}, []interface{}{"a"}, []interface{}{"b"}, []interface{}{"a"}},
|
||||||
|
"nil array": []interface{}{[]interface{}{}, []interface{}{nil}, []interface{}{nil, nil}, []interface{}{nil}, []interface{}{"a"}},
|
||||||
|
"multiple duplicates": []interface{}{map[string]interface{}{"a": "b"}, map[string]interface{}{"a": "c"}, map[string]interface{}{"a": "b"}, map[string]interface{}{"a": "c"}, map[string]interface{}{"a": "c"}},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"strings": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"integers": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"booleans": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"float64": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "number",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
}, Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty maps": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"map values": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil values": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple duplicates": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root", "strings[2]"),
|
||||||
|
duplicate("root", "integers[2]"),
|
||||||
|
duplicate("root", "booleans[2]"),
|
||||||
|
duplicate("root", "float64[2]"),
|
||||||
|
duplicate("root", "nil[2]"),
|
||||||
|
duplicate("root", "empty maps[2]"),
|
||||||
|
duplicate("root", "map values[2]"),
|
||||||
|
duplicate("root", "array[3]"),
|
||||||
|
duplicate("root", "nil array[3]"),
|
||||||
|
duplicate("root", "multiple duplicates[2]"),
|
||||||
|
duplicate("root", "multiple duplicates[3]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "set list with compound array items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"array": []interface{}{[]interface{}{}, []interface{}{"a"}, []interface{}{"a"}},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("set"),
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
duplicate("root", "array[2]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "map list with compound map items",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"strings": []interface{}{"a"},
|
||||||
|
"integers": []interface{}{int64(1)},
|
||||||
|
"booleans": []interface{}{false},
|
||||||
|
"float64": []interface{}{float64(1.0)},
|
||||||
|
"nil": []interface{}{nil},
|
||||||
|
"array": []interface{}{[]interface{}{"a"}},
|
||||||
|
"one key": []interface{}{map[string]interface{}{"a": "0", "c": "2"}, map[string]interface{}{"a": "1", "c": "1"}, map[string]interface{}{"a": "1", "c": "2"}, map[string]interface{}{}},
|
||||||
|
"two keys": []interface{}{map[string]interface{}{"a": "1", "b": "1", "c": "1"}, map[string]interface{}{"a": "1", "b": "2", "c": "2"}, map[string]interface{}{"a": "1", "b": "2", "c": "3"}, map[string]interface{}{}},
|
||||||
|
"undefined key": []interface{}{map[string]interface{}{"a": "1", "b": "1", "c": "1"}, map[string]interface{}{"a": "1", "c": "2"}, map[string]interface{}{"a": "1", "c": "3"}, map[string]interface{}{}},
|
||||||
|
"compound key": []interface{}{map[string]interface{}{"a": []interface{}{}, "c": "1"}, map[string]interface{}{"a": nil, "c": "1"}, map[string]interface{}{"a": []interface{}{"a"}, "c": "1"}, map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": "2"}, map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": []interface{}{"3"}}},
|
||||||
|
"nil key": []interface{}{map[string]interface{}{"a": []interface{}{}, "c": "1"}, map[string]interface{}{"a": nil, "c": "1"}, map[string]interface{}{"c": "1"}, map[string]interface{}{"a": nil}},
|
||||||
|
"nil item": []interface{}{nil, map[string]interface{}{"a": "0", "c": "1"}, map[string]interface{}{"a": nil}, map[string]interface{}{"c": "1"}},
|
||||||
|
"nil item multiple keys": []interface{}{nil, map[string]interface{}{"b": "0", "c": "1"}, map[string]interface{}{"a": nil}, map[string]interface{}{"c": "1"}},
|
||||||
|
"multiple duplicates": []interface{}{
|
||||||
|
map[string]interface{}{"a": []interface{}{}, "c": "1"},
|
||||||
|
map[string]interface{}{"a": nil, "c": "1"},
|
||||||
|
map[string]interface{}{"a": []interface{}{"a"}, "c": "1"},
|
||||||
|
map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": "2"},
|
||||||
|
map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": []interface{}{"3"}},
|
||||||
|
map[string]interface{}{"a": []interface{}{"a"}, "c": "1", "d": "1"},
|
||||||
|
map[string]interface{}{"a": []interface{}{"a"}, "c": "1", "d": "2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"strings": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"integers": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"booleans": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"float64": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "number",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"array": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"one key": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"two keys": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"undefined key": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"compound key": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil key": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"a": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil item": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"a": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil item multiple keys": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"a": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple duplicates": {
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
Items: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "object",
|
||||||
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
|
Structural: &schema.Structural{
|
||||||
|
Generic: schema.Generic{
|
||||||
|
Type: "string",
|
||||||
|
Nullable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
invalid("root", "strings[0]"),
|
||||||
|
invalid("root", "integers[0]"),
|
||||||
|
invalid("root", "booleans[0]"),
|
||||||
|
invalid("root", "float64[0]"),
|
||||||
|
invalid("root", "array[0]"),
|
||||||
|
duplicate("root", "one key[2]"),
|
||||||
|
duplicate("root", "two keys[2]"),
|
||||||
|
duplicate("root", "undefined key[2]"),
|
||||||
|
duplicate("root", "compound key[4]"),
|
||||||
|
duplicate("root", "nil key[3]"),
|
||||||
|
duplicate("root", "nil item[3]"),
|
||||||
|
duplicate("root", "nil item multiple keys[3]"),
|
||||||
|
duplicate("root", "multiple duplicates[4]"),
|
||||||
|
duplicate("root", "multiple duplicates[5]"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errs := ValidateListSetsAndMaps(field.NewPath("root"), tt.schema, tt.obj)
|
||||||
|
|
||||||
|
seenErrs := make([]bool, len(errs))
|
||||||
|
|
||||||
|
for _, expectedError := range tt.errors {
|
||||||
|
found := false
|
||||||
|
for i, err := range errs {
|
||||||
|
if expectedError.matches(err) && !seenErrs[i] {
|
||||||
|
found = true
|
||||||
|
seenErrs[i] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, seen := range seenErrs {
|
||||||
|
if !seen {
|
||||||
|
t.Errorf("unexpected error: %v", errs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type validationMatch struct {
|
||||||
|
path *field.Path
|
||||||
|
errorType field.ErrorType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v validationMatch) matches(err *field.Error) bool {
|
||||||
|
return err.Type == v.errorType && err.Field == v.path.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func duplicate(path ...string) validationMatch {
|
||||||
|
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeDuplicate}
|
||||||
|
}
|
||||||
|
func invalid(path ...string) validationMatch {
|
||||||
|
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
|
||||||
|
}
|
||||||
|
|
||||||
|
func strPtr(s string) *string { return &s }
|
@ -20,6 +20,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
|
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
|
||||||
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,14 +44,14 @@ type customResourceStrategy struct {
|
|||||||
runtime.ObjectTyper
|
runtime.ObjectTyper
|
||||||
names.NameGenerator
|
names.NameGenerator
|
||||||
|
|
||||||
namespaceScoped bool
|
namespaceScoped bool
|
||||||
validator customResourceValidator
|
validator customResourceValidator
|
||||||
schemas map[string]*structuralschema.Structural
|
structuralSchemas map[string]*structuralschema.Structural
|
||||||
status *apiextensions.CustomResourceSubresourceStatus
|
status *apiextensions.CustomResourceSubresourceStatus
|
||||||
scale *apiextensions.CustomResourceSubresourceScale
|
scale *apiextensions.CustomResourceSubresourceScale
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, schemaValidator, statusSchemaValidator *validate.SchemaValidator, schemas map[string]*structuralschema.Structural, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy {
|
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, schemaValidator, statusSchemaValidator *validate.SchemaValidator, structuralSchemas map[string]*structuralschema.Structural, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy {
|
||||||
return customResourceStrategy{
|
return customResourceStrategy{
|
||||||
ObjectTyper: typer,
|
ObjectTyper: typer,
|
||||||
NameGenerator: names.SimpleNameGenerator,
|
NameGenerator: names.SimpleNameGenerator,
|
||||||
@ -63,7 +64,7 @@ func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.Gr
|
|||||||
schemaValidator: schemaValidator,
|
schemaValidator: schemaValidator,
|
||||||
statusSchemaValidator: statusSchemaValidator,
|
statusSchemaValidator: statusSchemaValidator,
|
||||||
},
|
},
|
||||||
schemas: schemas,
|
structuralSchemas: structuralSchemas,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +138,10 @@ func (a customResourceStrategy) Validate(ctx context.Context, obj runtime.Object
|
|||||||
// validate embedded resources
|
// validate embedded resources
|
||||||
if u, ok := obj.(*unstructured.Unstructured); ok {
|
if u, ok := obj.(*unstructured.Unstructured); ok {
|
||||||
v := obj.GetObjectKind().GroupVersionKind().Version
|
v := obj.GetObjectKind().GroupVersionKind().Version
|
||||||
errs = append(errs, schemaobjectmeta.Validate(nil, u.Object, a.schemas[v], false)...)
|
errs = append(errs, schemaobjectmeta.Validate(nil, u.Object, a.structuralSchemas[v], false)...)
|
||||||
|
|
||||||
|
// validate x-kubernetes-list-type "map" and "set" invariant
|
||||||
|
errs = append(errs, structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchemas[v], u.Object)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
@ -163,10 +167,22 @@ func (a customResourceStrategy) ValidateUpdate(ctx context.Context, obj, old run
|
|||||||
var errs field.ErrorList
|
var errs field.ErrorList
|
||||||
errs = append(errs, a.validator.ValidateUpdate(ctx, obj, old, a.scale)...)
|
errs = append(errs, a.validator.ValidateUpdate(ctx, obj, old, a.scale)...)
|
||||||
|
|
||||||
|
uNew, ok := obj.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
uOld, ok := old.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
// Checks the embedded objects. We don't make a difference between update and create for those.
|
// Checks the embedded objects. We don't make a difference between update and create for those.
|
||||||
if u, ok := obj.(*unstructured.Unstructured); ok {
|
v := obj.GetObjectKind().GroupVersionKind().Version
|
||||||
v := obj.GetObjectKind().GroupVersionKind().Version
|
errs = append(errs, schemaobjectmeta.Validate(nil, uNew.Object, a.structuralSchemas[v], false)...)
|
||||||
errs = append(errs, schemaobjectmeta.Validate(nil, u.Object, a.schemas[v], false)...)
|
|
||||||
|
// ratcheting validation of x-kubernetes-list-type value map and set
|
||||||
|
if oldErrs := structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchemas[v], uOld.Object); len(oldErrs) == 0 {
|
||||||
|
errs = append(errs, structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchemas[v], uNew.Object)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
|
@ -16,6 +16,7 @@ go_test(
|
|||||||
"defaulting_test.go",
|
"defaulting_test.go",
|
||||||
"finalization_test.go",
|
"finalization_test.go",
|
||||||
"limit_test.go",
|
"limit_test.go",
|
||||||
|
"listtype_test.go",
|
||||||
"objectmeta_test.go",
|
"objectmeta_test.go",
|
||||||
"pruning_test.go",
|
"pruning_test.go",
|
||||||
"registration_test.go",
|
"registration_test.go",
|
||||||
@ -56,6 +57,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/util/retry:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/client-go/util/retry"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||||
|
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var listTypeResourceFixture = &apiextensionsv1beta1.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foos.tests.example.com"},
|
||||||
|
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
||||||
|
Group: "tests.example.com",
|
||||||
|
Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "v1beta1",
|
||||||
|
Storage: true,
|
||||||
|
Served: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
||||||
|
Plural: "foos",
|
||||||
|
Singular: "foo",
|
||||||
|
Kind: "Foo",
|
||||||
|
ListKind: "FooList",
|
||||||
|
},
|
||||||
|
Scope: apiextensionsv1beta1.ClusterScoped,
|
||||||
|
Validation: &apiextensionsv1beta1.CustomResourceValidation{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// structural schema because x-kubernetes-list-type is only allowed for those
|
||||||
|
listTypeResourceSchema = `
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
correct-map:
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: map
|
||||||
|
x-kubernetes-list-map-keys: ["a", "b"]
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
a:
|
||||||
|
type: integer
|
||||||
|
b:
|
||||||
|
type: integer
|
||||||
|
correct-set:
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: set
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-map-type: atomic
|
||||||
|
additionalProperties:
|
||||||
|
type: integer
|
||||||
|
invalid-map:
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: map
|
||||||
|
x-kubernetes-list-map-keys: ["a", "b"]
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
a:
|
||||||
|
type: integer
|
||||||
|
b:
|
||||||
|
type: integer
|
||||||
|
invalid-set:
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: set
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-map-type: atomic
|
||||||
|
additionalProperties:
|
||||||
|
type: integer
|
||||||
|
`
|
||||||
|
|
||||||
|
listTypeResourceInstance = `
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: tests.example.com/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
correct-map: [{"a":1,"b":1,c:"1"},{"a":1,"b":2,c:"2"},{"a":1,c:"3"}]
|
||||||
|
correct-set: [{"a":1,"b":1},{"a":1,"b":2},{"a":1},{"a":1,"b":4}]
|
||||||
|
invalid-map: [{"a":1,"b":1,c:"1"},{"a":1,"b":2,c:"2"},{"a":1,c:"3"},{"a":1,"b":1,c:"4"}]
|
||||||
|
invalid-set: [{"a":1,"b":1},{"a":1,"b":2},{"a":1},{"a":1,"b":4},{"a":1,"b":1}]
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validListTypeFields = []string{"correct-map", "correct-set"}
|
||||||
|
invalidListTypeFields = []string{"invalid-map", "invalid-set"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListTypes(t *testing.T) {
|
||||||
|
tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
crd := listTypeResourceFixture.DeepCopy()
|
||||||
|
if err := yaml.Unmarshal([]byte(listTypeResourceSchema), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Creating CR and expect list-type errors")
|
||||||
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Versions[0].Name, crd.Spec.Names.Plural})
|
||||||
|
invalidInstance := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(listTypeResourceInstance), &invalidInstance.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, createErr := fooClient.Create(invalidInstance, metav1.CreateOptions{})
|
||||||
|
if createErr == nil {
|
||||||
|
t.Fatalf("Expected validation errors, but did not get one")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Checking that valid fields DO NOT show in error")
|
||||||
|
for _, valid := range validListTypeFields {
|
||||||
|
if strings.Contains(createErr.Error(), valid) {
|
||||||
|
t.Errorf("unexpected error about %q: %v", valid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Checking that invalid fields DO show in error")
|
||||||
|
for _, invalid := range invalidListTypeFields {
|
||||||
|
if !strings.Contains(createErr.Error(), invalid) {
|
||||||
|
t.Errorf("expected %q to show up in the error, but didn't: %v", invalid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Creating fixed CR")
|
||||||
|
validInstance := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(listTypeResourceInstance), &validInstance.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, invalid := range invalidListTypeFields {
|
||||||
|
unstructured.RemoveNestedField(validInstance.Object, invalid)
|
||||||
|
}
|
||||||
|
validInstance, err = fooClient.Create(validInstance, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Updating with invalid values and expecting errors")
|
||||||
|
modifiedInstance := validInstance.DeepCopy()
|
||||||
|
for _, valid := range validListTypeFields {
|
||||||
|
x := modifiedInstance.Object[valid]
|
||||||
|
l := x.([]interface{})
|
||||||
|
l = append(l, l[0])
|
||||||
|
modifiedInstance.Object[valid] = l
|
||||||
|
}
|
||||||
|
_, err = fooClient.Update(modifiedInstance, metav1.UpdateOptions{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected validation errors, but did not get one")
|
||||||
|
}
|
||||||
|
for _, valid := range validListTypeFields {
|
||||||
|
if !strings.Contains(err.Error(), valid) {
|
||||||
|
t.Errorf("expected %q to show up in the error, but didn't: %v", valid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Remove \"b\" from the keys in the schema which renders the valid instance invalid")
|
||||||
|
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||||
|
crd, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(context.TODO(), crd.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s := crd.Spec.Validation.OpenAPIV3Schema.Properties["correct-map"]
|
||||||
|
s.XListMapKeys = []string{"a"}
|
||||||
|
crd.Spec.Validation.OpenAPIV3Schema.Properties["correct-map"] = s
|
||||||
|
_, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Updating again with invalid values, eventually successfully due to ratcheting logic")
|
||||||
|
err = wait.PollImmediate(time.Millisecond*100, wait.ForeverTestTimeout, func() (bool, error) {
|
||||||
|
_, err = fooClient.Update(modifiedInstance, metav1.UpdateOptions{})
|
||||||
|
if err == nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if errors.IsInvalid(err) {
|
||||||
|
// wait until modifiedInstance becomes valid again
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1168,6 +1168,7 @@ k8s.io/apiextensions-apiserver/pkg/apiserver
|
|||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/conversion
|
k8s.io/apiextensions-apiserver/pkg/apiserver/conversion
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema
|
k8s.io/apiextensions-apiserver/pkg/apiserver/schema
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting
|
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting
|
||||||
|
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta
|
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning
|
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/validation
|
k8s.io/apiextensions-apiserver/pkg/apiserver/validation
|
||||||
|
Loading…
Reference in New Issue
Block a user