github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/api/validation/schema.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"reflect"
    23  	"regexp"
    24  	"strings"
    25  
    26  	"github.com/emicklei/go-restful/swagger"
    27  	"github.com/golang/glog"
    28  	apiutil "k8s.io/kubernetes/pkg/api/util"
    29  	utilerrors "k8s.io/kubernetes/pkg/util/errors"
    30  	"k8s.io/kubernetes/pkg/util/yaml"
    31  )
    32  
    33  type InvalidTypeError struct {
    34  	ExpectedKind reflect.Kind
    35  	ObservedKind reflect.Kind
    36  	FieldName    string
    37  }
    38  
    39  func (i *InvalidTypeError) Error() string {
    40  	return fmt.Sprintf("expected type %s, for field %s, got %s", i.ExpectedKind.String(), i.FieldName, i.ObservedKind.String())
    41  }
    42  
    43  func NewInvalidTypeError(expected reflect.Kind, observed reflect.Kind, fieldName string) error {
    44  	return &InvalidTypeError{expected, observed, fieldName}
    45  }
    46  
    47  // Schema is an interface that knows how to validate an API object serialized to a byte array.
    48  type Schema interface {
    49  	ValidateBytes(data []byte) error
    50  }
    51  
    52  type NullSchema struct{}
    53  
    54  func (NullSchema) ValidateBytes(data []byte) error { return nil }
    55  
    56  type SwaggerSchema struct {
    57  	api swagger.ApiDeclaration
    58  }
    59  
    60  func NewSwaggerSchemaFromBytes(data []byte) (Schema, error) {
    61  	schema := &SwaggerSchema{}
    62  	err := json.Unmarshal(data, &schema.api)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	return schema, nil
    67  }
    68  
    69  // validateList unpacks a list and validate every item in the list.
    70  // It return nil if every item is ok.
    71  // Otherwise it return an error list contain errors of every item.
    72  func (s *SwaggerSchema) validateList(obj map[string]interface{}) []error {
    73  	allErrs := []error{}
    74  	items, exists := obj["items"]
    75  	if !exists {
    76  		return append(allErrs, fmt.Errorf("no items field in %#v", obj))
    77  	}
    78  	itemList, ok := items.([]interface{})
    79  	if !ok {
    80  		return append(allErrs, fmt.Errorf("items isn't a slice"))
    81  	}
    82  	for i, item := range itemList {
    83  		fields, ok := item.(map[string]interface{})
    84  		if !ok {
    85  			allErrs = append(allErrs, fmt.Errorf("items[%d] isn't a map[string]interface{}", i))
    86  			continue
    87  		}
    88  		groupVersion := fields["apiVersion"]
    89  		if groupVersion == nil {
    90  			allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion not set", i))
    91  			continue
    92  		}
    93  		itemVersion, ok := groupVersion.(string)
    94  		if !ok {
    95  			allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion isn't string type", i))
    96  			continue
    97  		}
    98  		if len(itemVersion) == 0 {
    99  			allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion is empty", i))
   100  		}
   101  		kind := fields["kind"]
   102  		if kind == nil {
   103  			allErrs = append(allErrs, fmt.Errorf("items[%d].kind not set", i))
   104  			continue
   105  		}
   106  		itemKind, ok := kind.(string)
   107  		if !ok {
   108  			allErrs = append(allErrs, fmt.Errorf("items[%d].kind isn't string type", i))
   109  			continue
   110  		}
   111  		if len(itemKind) == 0 {
   112  			allErrs = append(allErrs, fmt.Errorf("items[%d].kind is empty", i))
   113  		}
   114  		version := apiutil.GetVersion(itemVersion)
   115  		errs := s.ValidateObject(item, "", version+"."+itemKind)
   116  		if len(errs) >= 1 {
   117  			allErrs = append(allErrs, errs...)
   118  		}
   119  	}
   120  	return allErrs
   121  }
   122  
   123  func (s *SwaggerSchema) ValidateBytes(data []byte) error {
   124  	var obj interface{}
   125  	out, err := yaml.ToJSON(data)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	data = out
   130  	if err := json.Unmarshal(data, &obj); err != nil {
   131  		return err
   132  	}
   133  	fields, ok := obj.(map[string]interface{})
   134  	if !ok {
   135  		return fmt.Errorf("error in unmarshaling data %s", string(data))
   136  	}
   137  	groupVersion := fields["apiVersion"]
   138  	if groupVersion == nil {
   139  		return fmt.Errorf("apiVersion not set")
   140  	}
   141  	if _, ok := groupVersion.(string); !ok {
   142  		return fmt.Errorf("apiVersion isn't string type")
   143  	}
   144  	kind := fields["kind"]
   145  	if kind == nil {
   146  		return fmt.Errorf("kind not set")
   147  	}
   148  	if _, ok := kind.(string); !ok {
   149  		return fmt.Errorf("kind isn't string type")
   150  	}
   151  	if strings.HasSuffix(kind.(string), "List") {
   152  		return utilerrors.NewAggregate(s.validateList(fields))
   153  	}
   154  	version := apiutil.GetVersion(groupVersion.(string))
   155  	allErrs := s.ValidateObject(obj, "", version+"."+kind.(string))
   156  	if len(allErrs) == 1 {
   157  		return allErrs[0]
   158  	}
   159  	return utilerrors.NewAggregate(allErrs)
   160  }
   161  
   162  func (s *SwaggerSchema) ValidateObject(obj interface{}, fieldName, typeName string) []error {
   163  	allErrs := []error{}
   164  	models := s.api.Models
   165  	model, ok := models.At(typeName)
   166  	if !ok {
   167  		return append(allErrs, fmt.Errorf("couldn't find type: %s", typeName))
   168  	}
   169  	properties := model.Properties
   170  	if len(properties.List) == 0 {
   171  		// The object does not have any sub-fields.
   172  		return nil
   173  	}
   174  	fields, ok := obj.(map[string]interface{})
   175  	if !ok {
   176  		return append(allErrs, fmt.Errorf("field %s: expected object of type map[string]interface{}, but the actual type is %T", fieldName, obj))
   177  	}
   178  	if len(fieldName) > 0 {
   179  		fieldName = fieldName + "."
   180  	}
   181  	// handle required fields
   182  	for _, requiredKey := range model.Required {
   183  		if _, ok := fields[requiredKey]; !ok {
   184  			allErrs = append(allErrs, fmt.Errorf("field %s: is required", requiredKey))
   185  		}
   186  	}
   187  	for key, value := range fields {
   188  		details, ok := properties.At(key)
   189  		if !ok {
   190  			allErrs = append(allErrs, fmt.Errorf("found invalid field %s for %s", key, typeName))
   191  			continue
   192  		}
   193  		if details.Type == nil && details.Ref == nil {
   194  			allErrs = append(allErrs, fmt.Errorf("could not find the type of %s from object: %v", key, details))
   195  		}
   196  		var fieldType string
   197  		if details.Type != nil {
   198  			fieldType = *details.Type
   199  		} else {
   200  			fieldType = *details.Ref
   201  		}
   202  		if value == nil {
   203  			glog.V(2).Infof("Skipping nil field: %s", key)
   204  			continue
   205  		}
   206  		errs := s.validateField(value, fieldName+key, fieldType, &details)
   207  		if len(errs) > 0 {
   208  			allErrs = append(allErrs, errs...)
   209  		}
   210  	}
   211  	return allErrs
   212  }
   213  
   214  // This matches type name in the swagger spec, such as "v1.Binding".
   215  var versionRegexp = regexp.MustCompile(`^v.+\..*`)
   216  
   217  func (s *SwaggerSchema) validateField(value interface{}, fieldName, fieldType string, fieldDetails *swagger.ModelProperty) []error {
   218  	// TODO: caesarxuchao: because we have multiple group/versions and objects
   219  	// may reference objects in other group, the commented out way of checking
   220  	// if a filedType is a type defined by us is outdated. We use a hacky way
   221  	// for now.
   222  	// TODO: the type name in the swagger spec is something like "v1.Binding",
   223  	// and the "v1" is generated from the package name, not the groupVersion of
   224  	// the type. We need to fix go-restful to embed the group name in the type
   225  	// name, otherwise we couldn't handle identically named types in different
   226  	// groups correctly.
   227  	if versionRegexp.MatchString(fieldType) {
   228  		// if strings.HasPrefix(fieldType, apiVersion) {
   229  		return s.ValidateObject(value, fieldName, fieldType)
   230  	}
   231  	allErrs := []error{}
   232  	switch fieldType {
   233  	case "string":
   234  		// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
   235  		_, isString := value.(string)
   236  		_, isNumber := value.(float64)
   237  		_, isInteger := value.(int)
   238  		if !isString && !isNumber && !isInteger {
   239  			return append(allErrs, NewInvalidTypeError(reflect.String, reflect.TypeOf(value).Kind(), fieldName))
   240  		}
   241  	case "array":
   242  		arr, ok := value.([]interface{})
   243  		if !ok {
   244  			return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
   245  		}
   246  		var arrType string
   247  		if fieldDetails.Items.Ref == nil && fieldDetails.Items.Type == nil {
   248  			return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
   249  		}
   250  		if fieldDetails.Items.Ref != nil {
   251  			arrType = *fieldDetails.Items.Ref
   252  		} else {
   253  			arrType = *fieldDetails.Items.Type
   254  		}
   255  		for ix := range arr {
   256  			errs := s.validateField(arr[ix], fmt.Sprintf("%s[%d]", fieldName, ix), arrType, nil)
   257  			if len(errs) > 0 {
   258  				allErrs = append(allErrs, errs...)
   259  			}
   260  		}
   261  	case "uint64":
   262  	case "int64":
   263  	case "integer":
   264  		_, isNumber := value.(float64)
   265  		_, isInteger := value.(int)
   266  		if !isNumber && !isInteger {
   267  			return append(allErrs, NewInvalidTypeError(reflect.Int, reflect.TypeOf(value).Kind(), fieldName))
   268  		}
   269  	case "float64":
   270  		if _, ok := value.(float64); !ok {
   271  			return append(allErrs, NewInvalidTypeError(reflect.Float64, reflect.TypeOf(value).Kind(), fieldName))
   272  		}
   273  	case "boolean":
   274  		if _, ok := value.(bool); !ok {
   275  			return append(allErrs, NewInvalidTypeError(reflect.Bool, reflect.TypeOf(value).Kind(), fieldName))
   276  		}
   277  	case "any":
   278  	default:
   279  		return append(allErrs, fmt.Errorf("unexpected type: %v", fieldType))
   280  	}
   281  	return allErrs
   282  }