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 }