github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/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  	"k8s.io/kubernetes/pkg/runtime"
    30  	utilerrors "k8s.io/kubernetes/pkg/util/errors"
    31  	"k8s.io/kubernetes/pkg/util/yaml"
    32  )
    33  
    34  type InvalidTypeError struct {
    35  	ExpectedKind reflect.Kind
    36  	ObservedKind reflect.Kind
    37  	FieldName    string
    38  }
    39  
    40  func (i *InvalidTypeError) Error() string {
    41  	return fmt.Sprintf("expected type %s, for field %s, got %s", i.ExpectedKind.String(), i.FieldName, i.ObservedKind.String())
    42  }
    43  
    44  func NewInvalidTypeError(expected reflect.Kind, observed reflect.Kind, fieldName string) error {
    45  	return &InvalidTypeError{expected, observed, fieldName}
    46  }
    47  
    48  // TypeNotFoundError is returned when specified type
    49  // can not found in schema
    50  type TypeNotFoundError string
    51  
    52  func (tnfe TypeNotFoundError) Error() string {
    53  	return fmt.Sprintf("couldn't find type: %s", string(tnfe))
    54  }
    55  
    56  // Schema is an interface that knows how to validate an API object serialized to a byte array.
    57  type Schema interface {
    58  	ValidateBytes(data []byte) error
    59  }
    60  
    61  type NullSchema struct{}
    62  
    63  func (NullSchema) ValidateBytes(data []byte) error { return nil }
    64  
    65  type SwaggerSchema struct {
    66  	api      swagger.ApiDeclaration
    67  	delegate Schema // For delegating to other api groups
    68  }
    69  
    70  func NewSwaggerSchemaFromBytes(data []byte, factory Schema) (Schema, error) {
    71  	schema := &SwaggerSchema{}
    72  	err := json.Unmarshal(data, &schema.api)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	schema.delegate = factory
    77  	return schema, nil
    78  }
    79  
    80  // validateList unpacks a list and validate every item in the list.
    81  // It return nil if every item is ok.
    82  // Otherwise it return an error list contain errors of every item.
    83  func (s *SwaggerSchema) validateList(obj map[string]interface{}) []error {
    84  	items, exists := obj["items"]
    85  	if !exists {
    86  		return []error{fmt.Errorf("no items field in %#v", obj)}
    87  	}
    88  	return s.validateItems(items)
    89  }
    90  
    91  func (s *SwaggerSchema) validateItems(items interface{}) []error {
    92  	allErrs := []error{}
    93  	itemList, ok := items.([]interface{})
    94  	if !ok {
    95  		return append(allErrs, fmt.Errorf("items isn't a slice"))
    96  	}
    97  	for i, item := range itemList {
    98  		fields, ok := item.(map[string]interface{})
    99  		if !ok {
   100  			allErrs = append(allErrs, fmt.Errorf("items[%d] isn't a map[string]interface{}", i))
   101  			continue
   102  		}
   103  		groupVersion := fields["apiVersion"]
   104  		if groupVersion == nil {
   105  			allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion not set", i))
   106  			continue
   107  		}
   108  		itemVersion, ok := groupVersion.(string)
   109  		if !ok {
   110  			allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion isn't string type", i))
   111  			continue
   112  		}
   113  		if len(itemVersion) == 0 {
   114  			allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion is empty", i))
   115  		}
   116  		kind := fields["kind"]
   117  		if kind == nil {
   118  			allErrs = append(allErrs, fmt.Errorf("items[%d].kind not set", i))
   119  			continue
   120  		}
   121  		itemKind, ok := kind.(string)
   122  		if !ok {
   123  			allErrs = append(allErrs, fmt.Errorf("items[%d].kind isn't string type", i))
   124  			continue
   125  		}
   126  		if len(itemKind) == 0 {
   127  			allErrs = append(allErrs, fmt.Errorf("items[%d].kind is empty", i))
   128  		}
   129  		version := apiutil.GetVersion(itemVersion)
   130  		errs := s.ValidateObject(item, "", version+"."+itemKind)
   131  		if len(errs) >= 1 {
   132  			allErrs = append(allErrs, errs...)
   133  		}
   134  	}
   135  
   136  	return allErrs
   137  }
   138  
   139  func (s *SwaggerSchema) ValidateBytes(data []byte) error {
   140  	var obj interface{}
   141  	out, err := yaml.ToJSON(data)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	data = out
   146  	if err := json.Unmarshal(data, &obj); err != nil {
   147  		return err
   148  	}
   149  	fields, ok := obj.(map[string]interface{})
   150  	if !ok {
   151  		return fmt.Errorf("error in unmarshaling data %s", string(data))
   152  	}
   153  	groupVersion := fields["apiVersion"]
   154  	if groupVersion == nil {
   155  		return fmt.Errorf("apiVersion not set")
   156  	}
   157  	if _, ok := groupVersion.(string); !ok {
   158  		return fmt.Errorf("apiVersion isn't string type")
   159  	}
   160  	kind := fields["kind"]
   161  	if kind == nil {
   162  		return fmt.Errorf("kind not set")
   163  	}
   164  	if _, ok := kind.(string); !ok {
   165  		return fmt.Errorf("kind isn't string type")
   166  	}
   167  	if strings.HasSuffix(kind.(string), "List") {
   168  		return utilerrors.NewAggregate(s.validateList(fields))
   169  	}
   170  	version := apiutil.GetVersion(groupVersion.(string))
   171  	allErrs := s.ValidateObject(obj, "", version+"."+kind.(string))
   172  	if len(allErrs) == 1 {
   173  		return allErrs[0]
   174  	}
   175  	return utilerrors.NewAggregate(allErrs)
   176  }
   177  
   178  func (s *SwaggerSchema) ValidateObject(obj interface{}, fieldName, typeName string) []error {
   179  	allErrs := []error{}
   180  	models := s.api.Models
   181  	model, ok := models.At(typeName)
   182  
   183  	// Verify the api version matches.  This is required for nested types with differing api versions because
   184  	// s.api only has schema for 1 api version (the parent object type's version).
   185  	// e.g. an extensions/v1beta1 Template embedding a /v1 Service requires the schema for the extensions/v1beta1
   186  	// api to delegate to the schema for the /v1 api.
   187  	// Only do this for !ok objects so that cross ApiVersion vendored types take precedence.
   188  	if !ok && s.delegate != nil {
   189  		fields, mapOk := obj.(map[string]interface{})
   190  		if !mapOk {
   191  			return append(allErrs, fmt.Errorf("field %s: expected object of type map[string]interface{}, but the actual type is %T", fieldName, obj))
   192  		}
   193  		if delegated, err := s.delegateIfDifferentApiVersion(runtime.Unstructured{Object: fields}); delegated {
   194  			if err != nil {
   195  				allErrs = append(allErrs, err)
   196  			}
   197  			return allErrs
   198  		}
   199  	}
   200  
   201  	if !ok {
   202  		return append(allErrs, TypeNotFoundError(typeName))
   203  	}
   204  	properties := model.Properties
   205  	if len(properties.List) == 0 {
   206  		// The object does not have any sub-fields.
   207  		return nil
   208  	}
   209  	fields, ok := obj.(map[string]interface{})
   210  	if !ok {
   211  		return append(allErrs, fmt.Errorf("field %s: expected object of type map[string]interface{}, but the actual type is %T", fieldName, obj))
   212  	}
   213  	if len(fieldName) > 0 {
   214  		fieldName = fieldName + "."
   215  	}
   216  	// handle required fields
   217  	for _, requiredKey := range model.Required {
   218  		if _, ok := fields[requiredKey]; !ok {
   219  			allErrs = append(allErrs, fmt.Errorf("field %s: is required", requiredKey))
   220  		}
   221  	}
   222  	for key, value := range fields {
   223  		details, ok := properties.At(key)
   224  
   225  		// Special case for runtime.RawExtension and runtime.Objects because they always fail to validate
   226  		// This is because the actual values will be of some sub-type (e.g. Deployment) not the expected
   227  		// super-type (RawExtention)
   228  		if s.isGenericArray(details) {
   229  			errs := s.validateItems(value)
   230  			if len(errs) > 0 {
   231  				allErrs = append(allErrs, errs...)
   232  			}
   233  			continue
   234  		}
   235  		if !ok {
   236  			allErrs = append(allErrs, fmt.Errorf("found invalid field %s for %s", key, typeName))
   237  			continue
   238  		}
   239  		if details.Type == nil && details.Ref == nil {
   240  			allErrs = append(allErrs, fmt.Errorf("could not find the type of %s from object: %v", key, details))
   241  		}
   242  		var fieldType string
   243  		if details.Type != nil {
   244  			fieldType = *details.Type
   245  		} else {
   246  			fieldType = *details.Ref
   247  		}
   248  		if value == nil {
   249  			glog.V(2).Infof("Skipping nil field: %s", key)
   250  			continue
   251  		}
   252  		errs := s.validateField(value, fieldName+key, fieldType, &details)
   253  		if len(errs) > 0 {
   254  			allErrs = append(allErrs, errs...)
   255  		}
   256  	}
   257  	return allErrs
   258  }
   259  
   260  // delegateIfDifferentApiVersion delegates the validation of an object if its ApiGroup does not match the
   261  // current SwaggerSchema.
   262  // First return value is true if the validation was delegated (by a different ApiGroup SwaggerSchema)
   263  // Second return value is the result of the delegated validation if performed.
   264  func (s *SwaggerSchema) delegateIfDifferentApiVersion(obj runtime.Unstructured) (bool, error) {
   265  	// Never delegate objects in the same ApiVersion or we will get infinite recursion
   266  	if !s.isDifferentApiVersion(obj) {
   267  		return false, nil
   268  	}
   269  
   270  	// Convert the object back into bytes so that we can pass it to the ValidateBytes function
   271  	m, err := json.Marshal(obj.Object)
   272  	if err != nil {
   273  		return true, err
   274  	}
   275  
   276  	// Delegate validation of this object to the correct SwaggerSchema for its ApiGroup
   277  	return true, s.delegate.ValidateBytes(m)
   278  }
   279  
   280  // isDifferentApiVersion Returns true if obj lives in a different ApiVersion than the SwaggerSchema does.
   281  // The SwaggerSchema will not be able to process objects in different ApiVersions unless they are vendored.
   282  func (s *SwaggerSchema) isDifferentApiVersion(obj runtime.Unstructured) bool {
   283  	groupVersion := obj.GetAPIVersion()
   284  	return len(groupVersion) > 0 && s.api.ApiVersion != groupVersion
   285  }
   286  
   287  // isGenericArray Returns true if p is an array of generic Objects - either RawExtension or Object.
   288  func (s *SwaggerSchema) isGenericArray(p swagger.ModelProperty) bool {
   289  	return p.DataTypeFields.Type != nil &&
   290  		*p.DataTypeFields.Type == "array" &&
   291  		p.Items != nil &&
   292  		p.Items.Ref != nil &&
   293  		(*p.Items.Ref == "runtime.RawExtension" || *p.Items.Ref == "runtime.Object")
   294  }
   295  
   296  // This matches type name in the swagger spec, such as "v1.Binding".
   297  var versionRegexp = regexp.MustCompile(`^v.+\..*`)
   298  
   299  func (s *SwaggerSchema) validateField(value interface{}, fieldName, fieldType string, fieldDetails *swagger.ModelProperty) []error {
   300  	// TODO: caesarxuchao: because we have multiple group/versions and objects
   301  	// may reference objects in other group, the commented out way of checking
   302  	// if a filedType is a type defined by us is outdated. We use a hacky way
   303  	// for now.
   304  	// TODO: the type name in the swagger spec is something like "v1.Binding",
   305  	// and the "v1" is generated from the package name, not the groupVersion of
   306  	// the type. We need to fix go-restful to embed the group name in the type
   307  	// name, otherwise we couldn't handle identically named types in different
   308  	// groups correctly.
   309  	if versionRegexp.MatchString(fieldType) {
   310  		// if strings.HasPrefix(fieldType, apiVersion) {
   311  		return s.ValidateObject(value, fieldName, fieldType)
   312  	}
   313  	allErrs := []error{}
   314  	switch fieldType {
   315  	case "string":
   316  		// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
   317  		_, isString := value.(string)
   318  		_, isNumber := value.(float64)
   319  		_, isInteger := value.(int)
   320  		if !isString && !isNumber && !isInteger {
   321  			return append(allErrs, NewInvalidTypeError(reflect.String, reflect.TypeOf(value).Kind(), fieldName))
   322  		}
   323  	case "array":
   324  		arr, ok := value.([]interface{})
   325  		if !ok {
   326  			return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
   327  		}
   328  		var arrType string
   329  		if fieldDetails.Items.Ref == nil && fieldDetails.Items.Type == nil {
   330  			return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
   331  		}
   332  		if fieldDetails.Items.Ref != nil {
   333  			arrType = *fieldDetails.Items.Ref
   334  		} else {
   335  			arrType = *fieldDetails.Items.Type
   336  		}
   337  		for ix := range arr {
   338  			errs := s.validateField(arr[ix], fmt.Sprintf("%s[%d]", fieldName, ix), arrType, nil)
   339  			if len(errs) > 0 {
   340  				allErrs = append(allErrs, errs...)
   341  			}
   342  		}
   343  	case "uint64":
   344  	case "int64":
   345  	case "integer":
   346  		_, isNumber := value.(float64)
   347  		_, isInteger := value.(int)
   348  		if !isNumber && !isInteger {
   349  			return append(allErrs, NewInvalidTypeError(reflect.Int, reflect.TypeOf(value).Kind(), fieldName))
   350  		}
   351  	case "float64":
   352  		if _, ok := value.(float64); !ok {
   353  			return append(allErrs, NewInvalidTypeError(reflect.Float64, reflect.TypeOf(value).Kind(), fieldName))
   354  		}
   355  	case "boolean":
   356  		if _, ok := value.(bool); !ok {
   357  			return append(allErrs, NewInvalidTypeError(reflect.Bool, reflect.TypeOf(value).Kind(), fieldName))
   358  		}
   359  	// API servers before release 1.3 produce swagger spec with `type: "any"` as the fallback type, while newer servers produce spec with `type: "object"`.
   360  	// We have both here so that kubectl can work with both old and new api servers.
   361  	case "object":
   362  	case "any":
   363  	default:
   364  		return append(allErrs, fmt.Errorf("unexpected type: %v", fieldType))
   365  	}
   366  	return allErrs
   367  }