Add some checking for the presence of the same key twice.

This commit is contained in:
Brendan Burns 2016-10-13 21:58:44 -07:00
parent a5ec367c30
commit d9d06f6680
3 changed files with 185 additions and 1 deletions

View File

@ -17,6 +17,7 @@ limitations under the License.
package validation
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
@ -24,6 +25,7 @@ import (
"strings"
"github.com/emicklei/go-restful/swagger"
ejson "github.com/exponent-io/jsonpath"
"github.com/golang/glog"
apiutil "k8s.io/kubernetes/pkg/api/util"
"k8s.io/kubernetes/pkg/runtime"
@ -62,6 +64,69 @@ type NullSchema struct{}
func (NullSchema) ValidateBytes(data []byte) error { return nil }
type NoDoubleKeySchema struct{}
func (NoDoubleKeySchema) ValidateBytes(data []byte) error {
var list []error = nil
if err := validateNoDuplicateKeys(data, "metadata", "labels"); err != nil {
list = append(list, err)
}
if err := validateNoDuplicateKeys(data, "metadata", "annotations"); err != nil {
list = append(list, err)
}
return utilerrors.NewAggregate(list)
}
func validateNoDuplicateKeys(data []byte, path ...string) error {
r := ejson.NewDecoder(bytes.NewReader(data))
// This is Go being unfriendly. The 'path ...string' comes in as a
// []string, and SeekTo takes ...interface{}, so we can't just pass
// the path straight in, we have to copy it. *sigh*
ifacePath := []interface{}{}
for ix := range path {
ifacePath = append(ifacePath, path[ix])
}
found, err := r.SeekTo(ifacePath...)
if err != nil {
return err
}
if !found {
return nil
}
seen := map[string]bool{}
for {
tok, err := r.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case json.Delim:
if t.String() == "}" {
return nil
}
case ejson.KeyString:
if seen[string(t)] {
return fmt.Errorf("duplicate key: %s", string(t))
} else {
seen[string(t)] = true
}
}
}
}
type ConjunctiveSchema []Schema
func (c ConjunctiveSchema) ValidateBytes(data []byte) error {
var list []error = nil
schemas := []Schema(c)
for ix := range schemas {
if err := schemas[ix].ValidateBytes(data); err != nil {
list = append(list, err)
}
}
return utilerrors.NewAggregate(list)
}
type SwaggerSchema struct {
api swagger.ApiDeclaration
delegate Schema // For delegating to other api groups

View File

@ -309,3 +309,118 @@ func TestTypeAny(t *testing.T) {
}
}
}
func TestValidateDuplicateLabelsFailCases(t *testing.T) {
strs := []string{
`{
"metadata": {
"labels": {
"foo": "bar",
"foo": "baz"
}
}
}`,
`{
"metadata": {
"annotations": {
"foo": "bar",
"foo": "baz"
}
}
}`,
`{
"metadata": {
"labels": {
"foo": "blah"
},
"annotations": {
"foo": "bar",
"foo": "baz"
}
}
}`,
}
schema := NoDoubleKeySchema{}
for _, str := range strs {
err := schema.ValidateBytes([]byte(str))
if err == nil {
t.Errorf("Unexpected non-error %s", str)
}
}
}
func TestValidateDuplicateLabelsPassCases(t *testing.T) {
strs := []string{
`{
"metadata": {
"labels": {
"foo": "bar"
},
"annotations": {
"foo": "baz"
}
}
}`,
`{
"metadata": {}
}`,
`{
"metadata": {
"labels": {}
}
}`,
}
schema := NoDoubleKeySchema{}
for _, str := range strs {
err := schema.ValidateBytes([]byte(str))
if err != nil {
t.Errorf("Unexpected error: %v %s", err, str)
}
}
}
type AlwaysInvalidSchema struct{}
func (AlwaysInvalidSchema) ValidateBytes([]byte) error {
return fmt.Errorf("Always invalid!")
}
func TestConjunctiveSchema(t *testing.T) {
tests := []struct {
schemas []Schema
shouldPass bool
name string
}{
{
schemas: []Schema{NullSchema{}, NullSchema{}},
shouldPass: true,
name: "all pass",
},
{
schemas: []Schema{NullSchema{}, AlwaysInvalidSchema{}},
shouldPass: false,
name: "one fail",
},
{
schemas: []Schema{AlwaysInvalidSchema{}, AlwaysInvalidSchema{}},
shouldPass: false,
name: "all fail",
},
{
schemas: []Schema{},
shouldPass: true,
name: "empty",
},
}
for _, test := range tests {
schema := ConjunctiveSchema(test.schemas)
err := schema.ValidateBytes([]byte{})
if err != nil && test.shouldPass {
t.Errorf("Unexpected error: %v in %s", err, test.name)
}
if err == nil && !test.shouldPass {
t.Errorf("Unexpected non-error: %s", test.name)
}
}
}

View File

@ -762,10 +762,14 @@ func (f *factory) Validator(validate bool, cacheDir string) (validation.Schema,
if err != nil {
return nil, err
}
return &clientSwaggerSchema{
swaggerSchema := &clientSwaggerSchema{
c: restclient,
fedc: fedClient,
cacheDir: dir,
}
return validation.ConjunctiveSchema{
swaggerSchema,
validation.NoDoubleKeySchema{},
}, nil
}
return validation.NullSchema{}, nil