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 }