2014-11-20 05:25:24 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2014-11-20 05:25:24 +00:00
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 validation
import (
"encoding/json"
"fmt"
"reflect"
2015-09-17 05:15:05 +00:00
"regexp"
2015-09-29 11:20:11 +00:00
"strings"
2014-11-20 05:25:24 +00:00
2015-08-05 22:05:17 +00:00
"github.com/emicklei/go-restful/swagger"
"github.com/golang/glog"
2015-09-17 05:15:05 +00:00
apiutil "k8s.io/kubernetes/pkg/api/util"
2015-10-14 05:18:37 +00:00
utilerrors "k8s.io/kubernetes/pkg/util/errors"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/util/yaml"
2014-11-20 05:25:24 +00:00
)
type InvalidTypeError struct {
ExpectedKind reflect . Kind
ObservedKind reflect . Kind
FieldName string
}
func ( i * InvalidTypeError ) Error ( ) string {
return fmt . Sprintf ( "expected type %s, for field %s, got %s" , i . ExpectedKind . String ( ) , i . FieldName , i . ObservedKind . String ( ) )
}
func NewInvalidTypeError ( expected reflect . Kind , observed reflect . Kind , fieldName string ) error {
return & InvalidTypeError { expected , observed , fieldName }
}
2016-03-04 23:59:40 +00:00
// TypeNotFoundError is returned when specified type
// can not found in schema
type TypeNotFoundError string
func ( tnfe TypeNotFoundError ) Error ( ) string {
return fmt . Sprintf ( "couldn't find type: %s" , string ( tnfe ) )
}
2014-11-26 03:50:31 +00:00
// Schema is an interface that knows how to validate an API object serialized to a byte array.
type Schema interface {
ValidateBytes ( data [ ] byte ) error
}
type NullSchema struct { }
func ( NullSchema ) ValidateBytes ( data [ ] byte ) error { return nil }
type SwaggerSchema struct {
2014-11-20 05:25:24 +00:00
api swagger . ApiDeclaration
}
2014-11-26 03:50:31 +00:00
func NewSwaggerSchemaFromBytes ( data [ ] byte ) ( Schema , error ) {
schema := & SwaggerSchema { }
2014-11-20 05:25:24 +00:00
err := json . Unmarshal ( data , & schema . api )
if err != nil {
return nil , err
}
return schema , nil
}
2015-11-04 00:08:20 +00:00
// validateList unpacks a list and validate every item in the list.
2015-09-29 11:20:11 +00:00
// It return nil if every item is ok.
// Otherwise it return an error list contain errors of every item.
2015-11-04 00:08:20 +00:00
func ( s * SwaggerSchema ) validateList ( obj map [ string ] interface { } ) [ ] error {
allErrs := [ ] error { }
2015-09-29 11:20:11 +00:00
items , exists := obj [ "items" ]
if ! exists {
return append ( allErrs , fmt . Errorf ( "no items field in %#v" , obj ) )
}
itemList , ok := items . ( [ ] interface { } )
if ! ok {
return append ( allErrs , fmt . Errorf ( "items isn't a slice" ) )
}
for i , item := range itemList {
fields , ok := item . ( map [ string ] interface { } )
if ! ok {
allErrs = append ( allErrs , fmt . Errorf ( "items[%d] isn't a map[string]interface{}" , i ) )
continue
}
groupVersion := fields [ "apiVersion" ]
if groupVersion == nil {
allErrs = append ( allErrs , fmt . Errorf ( "items[%d].apiVersion not set" , i ) )
continue
}
itemVersion , ok := groupVersion . ( string )
if ! ok {
allErrs = append ( allErrs , fmt . Errorf ( "items[%d].apiVersion isn't string type" , i ) )
continue
}
if len ( itemVersion ) == 0 {
allErrs = append ( allErrs , fmt . Errorf ( "items[%d].apiVersion is empty" , i ) )
}
kind := fields [ "kind" ]
if kind == nil {
allErrs = append ( allErrs , fmt . Errorf ( "items[%d].kind not set" , i ) )
continue
}
itemKind , ok := kind . ( string )
if ! ok {
allErrs = append ( allErrs , fmt . Errorf ( "items[%d].kind isn't string type" , i ) )
continue
}
if len ( itemKind ) == 0 {
allErrs = append ( allErrs , fmt . Errorf ( "items[%d].kind is empty" , i ) )
}
version := apiutil . GetVersion ( itemVersion )
errs := s . ValidateObject ( item , "" , version + "." + itemKind )
if len ( errs ) >= 1 {
allErrs = append ( allErrs , errs ... )
}
}
return allErrs
}
2014-11-26 03:50:31 +00:00
func ( s * SwaggerSchema ) ValidateBytes ( data [ ] byte ) error {
2014-11-20 05:25:24 +00:00
var obj interface { }
2015-03-17 03:43:59 +00:00
out , err := yaml . ToJSON ( data )
2014-11-20 05:25:24 +00:00
if err != nil {
return err
}
2015-03-17 03:43:59 +00:00
data = out
if err := json . Unmarshal ( data , & obj ) ; err != nil {
return err
}
2015-04-14 18:31:12 +00:00
fields , ok := obj . ( map [ string ] interface { } )
if ! ok {
return fmt . Errorf ( "error in unmarshaling data %s" , string ( data ) )
}
2015-09-17 05:15:05 +00:00
groupVersion := fields [ "apiVersion" ]
if groupVersion == nil {
2015-06-10 19:45:10 +00:00
return fmt . Errorf ( "apiVersion not set" )
2015-06-07 13:02:27 +00:00
}
2015-09-29 11:20:11 +00:00
if _ , ok := groupVersion . ( string ) ; ! ok {
return fmt . Errorf ( "apiVersion isn't string type" )
}
2015-06-07 13:02:27 +00:00
kind := fields [ "kind" ]
if kind == nil {
2015-06-10 19:45:10 +00:00
return fmt . Errorf ( "kind not set" )
2015-06-07 13:02:27 +00:00
}
2015-09-29 11:20:11 +00:00
if _ , ok := kind . ( string ) ; ! ok {
return fmt . Errorf ( "kind isn't string type" )
}
if strings . HasSuffix ( kind . ( string ) , "List" ) {
2015-10-14 05:18:37 +00:00
return utilerrors . NewAggregate ( s . validateList ( fields ) )
2015-09-29 11:20:11 +00:00
}
2015-09-17 05:15:05 +00:00
version := apiutil . GetVersion ( groupVersion . ( string ) )
allErrs := s . ValidateObject ( obj , "" , version + "." + kind . ( string ) )
2015-06-18 06:16:37 +00:00
if len ( allErrs ) == 1 {
return allErrs [ 0 ]
}
2015-10-14 05:18:37 +00:00
return utilerrors . NewAggregate ( allErrs )
2014-11-20 05:25:24 +00:00
}
2015-11-04 00:08:20 +00:00
func ( s * SwaggerSchema ) ValidateObject ( obj interface { } , fieldName , typeName string ) [ ] error {
allErrs := [ ] error { }
2014-11-20 05:25:24 +00:00
models := s . api . Models
2015-05-28 23:43:13 +00:00
model , ok := models . At ( typeName )
2014-11-20 05:25:24 +00:00
if ! ok {
2016-03-04 23:59:40 +00:00
return append ( allErrs , TypeNotFoundError ( typeName ) )
2014-11-20 05:25:24 +00:00
}
properties := model . Properties
2015-05-28 23:43:13 +00:00
if len ( properties . List ) == 0 {
2015-04-14 18:31:12 +00:00
// The object does not have any sub-fields.
return nil
}
fields , ok := obj . ( map [ string ] interface { } )
if ! ok {
2015-06-18 06:16:37 +00:00
return append ( allErrs , fmt . Errorf ( "field %s: expected object of type map[string]interface{}, but the actual type is %T" , fieldName , obj ) )
2015-04-14 18:31:12 +00:00
}
2014-11-20 05:25:24 +00:00
if len ( fieldName ) > 0 {
fieldName = fieldName + "."
}
2015-07-28 00:29:46 +00:00
// handle required fields
2015-06-18 06:16:37 +00:00
for _ , requiredKey := range model . Required {
if _ , ok := fields [ requiredKey ] ; ! ok {
allErrs = append ( allErrs , fmt . Errorf ( "field %s: is required" , requiredKey ) )
}
}
2014-11-20 05:25:24 +00:00
for key , value := range fields {
2015-05-28 23:43:13 +00:00
details , ok := properties . At ( key )
2014-11-20 05:25:24 +00:00
if ! ok {
2015-07-28 00:29:46 +00:00
allErrs = append ( allErrs , fmt . Errorf ( "found invalid field %s for %s" , key , typeName ) )
2014-11-20 05:25:24 +00:00
continue
}
2015-04-14 18:31:12 +00:00
if details . Type == nil && details . Ref == nil {
2015-06-18 06:16:37 +00:00
allErrs = append ( allErrs , fmt . Errorf ( "could not find the type of %s from object: %v" , key , details ) )
2015-04-14 18:31:12 +00:00
}
var fieldType string
if details . Type != nil {
fieldType = * details . Type
} else {
fieldType = * details . Ref
2015-03-27 22:47:40 +00:00
}
2014-11-20 05:25:24 +00:00
if value == nil {
glog . V ( 2 ) . Infof ( "Skipping nil field: %s" , key )
continue
}
2015-09-17 05:15:05 +00:00
errs := s . validateField ( value , fieldName + key , fieldType , & details )
2015-06-18 06:16:37 +00:00
if len ( errs ) > 0 {
allErrs = append ( allErrs , errs ... )
2014-11-20 05:25:24 +00:00
}
}
2015-06-18 06:16:37 +00:00
return allErrs
2014-11-20 05:25:24 +00:00
}
2015-09-17 05:15:05 +00:00
// This matches type name in the swagger spec, such as "v1.Binding".
var versionRegexp = regexp . MustCompile ( ` ^v.+\..* ` )
2015-11-04 00:08:20 +00:00
func ( s * SwaggerSchema ) validateField ( value interface { } , fieldName , fieldType string , fieldDetails * swagger . ModelProperty ) [ ] error {
2015-09-17 05:15:05 +00:00
// TODO: caesarxuchao: because we have multiple group/versions and objects
// may reference objects in other group, the commented out way of checking
// if a filedType is a type defined by us is outdated. We use a hacky way
// for now.
// TODO: the type name in the swagger spec is something like "v1.Binding",
// and the "v1" is generated from the package name, not the groupVersion of
// the type. We need to fix go-restful to embed the group name in the type
// name, otherwise we couldn't handle identically named types in different
// groups correctly.
if versionRegexp . MatchString ( fieldType ) {
// if strings.HasPrefix(fieldType, apiVersion) {
return s . ValidateObject ( value , fieldName , fieldType )
2014-11-20 05:25:24 +00:00
}
2015-11-04 00:08:20 +00:00
allErrs := [ ] error { }
2014-11-20 05:25:24 +00:00
switch fieldType {
case "string" :
// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
_ , isString := value . ( string )
_ , isNumber := value . ( float64 )
2014-11-26 04:10:21 +00:00
_ , isInteger := value . ( int )
if ! isString && ! isNumber && ! isInteger {
2015-06-18 06:16:37 +00:00
return append ( allErrs , NewInvalidTypeError ( reflect . String , reflect . TypeOf ( value ) . Kind ( ) , fieldName ) )
2014-11-20 05:25:24 +00:00
}
case "array" :
arr , ok := value . ( [ ] interface { } )
if ! ok {
2015-06-18 06:16:37 +00:00
return append ( allErrs , NewInvalidTypeError ( reflect . Array , reflect . TypeOf ( value ) . Kind ( ) , fieldName ) )
2014-11-20 05:25:24 +00:00
}
2015-04-14 18:31:12 +00:00
var arrType string
if fieldDetails . Items . Ref == nil && fieldDetails . Items . Type == nil {
2015-06-18 06:16:37 +00:00
return append ( allErrs , NewInvalidTypeError ( reflect . Array , reflect . TypeOf ( value ) . Kind ( ) , fieldName ) )
2015-04-14 18:31:12 +00:00
}
if fieldDetails . Items . Ref != nil {
arrType = * fieldDetails . Items . Ref
} else {
arrType = * fieldDetails . Items . Type
}
2014-11-20 05:25:24 +00:00
for ix := range arr {
2015-09-17 05:15:05 +00:00
errs := s . validateField ( arr [ ix ] , fmt . Sprintf ( "%s[%d]" , fieldName , ix ) , arrType , nil )
2015-06-18 06:16:37 +00:00
if len ( errs ) > 0 {
allErrs = append ( allErrs , errs ... )
2014-11-20 05:25:24 +00:00
}
}
case "uint64" :
2015-05-05 16:15:12 +00:00
case "int64" :
2014-11-20 05:25:24 +00:00
case "integer" :
2014-11-26 04:10:21 +00:00
_ , isNumber := value . ( float64 )
_ , isInteger := value . ( int )
if ! isNumber && ! isInteger {
2015-06-18 06:16:37 +00:00
return append ( allErrs , NewInvalidTypeError ( reflect . Int , reflect . TypeOf ( value ) . Kind ( ) , fieldName ) )
2014-11-20 05:25:24 +00:00
}
case "float64" :
if _ , ok := value . ( float64 ) ; ! ok {
2015-06-18 06:16:37 +00:00
return append ( allErrs , NewInvalidTypeError ( reflect . Float64 , reflect . TypeOf ( value ) . Kind ( ) , fieldName ) )
2014-11-20 05:25:24 +00:00
}
case "boolean" :
if _ , ok := value . ( bool ) ; ! ok {
2015-06-18 06:16:37 +00:00
return append ( allErrs , NewInvalidTypeError ( reflect . Bool , reflect . TypeOf ( value ) . Kind ( ) , fieldName ) )
2014-11-20 05:25:24 +00:00
}
2016-04-08 18:11:16 +00:00
// API servers before release 1.3 produce swagger spec with `type: "any"` as the fallback type, while newer servers produce spec with `type: "object"`.
// We have both here so that kubectl can work with both old and new api servers.
case "object" :
2016-04-06 01:08:46 +00:00
case "any" :
2014-11-20 05:25:24 +00:00
default :
2015-06-18 06:16:37 +00:00
return append ( allErrs , fmt . Errorf ( "unexpected type: %v" , fieldType ) )
2014-11-20 05:25:24 +00:00
}
2015-06-18 06:16:37 +00:00
return allErrs
2014-11-20 05:25:24 +00:00
}