package validation import ( "fmt" "reflect" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation/field" ) type RuntimeObjectValidator interface { Validate(obj runtime.Object) field.ErrorList ValidateUpdate(obj, old runtime.Object) field.ErrorList } var Validator = &RuntimeObjectsValidator{map[reflect.Type]RuntimeObjectValidatorInfo{}} type RuntimeObjectsValidator struct { typeToValidator map[reflect.Type]RuntimeObjectValidatorInfo } type RuntimeObjectValidatorInfo struct { Validator RuntimeObjectValidator IsNamespaced bool HasObjectMeta bool UpdateAllowed bool } func (v *RuntimeObjectsValidator) GetInfo(obj runtime.Object) (RuntimeObjectValidatorInfo, bool) { ret, ok := v.typeToValidator[reflect.TypeOf(obj)] return ret, ok } func (v *RuntimeObjectsValidator) MustRegister(obj runtime.Object, validateFunction interface{}, validateUpdateFunction interface{}) { if err := v.Register(obj, validateFunction, validateUpdateFunction); err != nil { panic(err) } } func (v *RuntimeObjectsValidator) Register(obj runtime.Object, validateFunction interface{}, validateUpdateFunction interface{}) error { objType := reflect.TypeOf(obj) if oldValidator, exists := v.typeToValidator[objType]; exists { panic(fmt.Sprintf("%v is already registered with %v", objType, oldValidator)) } validator, err := NewValidationWrapper(validateFunction, validateUpdateFunction) if err != nil { return err } isNamespaced, err := GetRequiresNamespace(obj) if err != nil { return err } updateAllowed := validateUpdateFunction != nil v.typeToValidator[objType] = RuntimeObjectValidatorInfo{validator, isNamespaced, HasObjectMeta(obj), updateAllowed} return nil } func (v *RuntimeObjectsValidator) Validate(obj runtime.Object) field.ErrorList { if obj == nil { return field.ErrorList{} } allErrs := field.ErrorList{} specificValidationInfo, err := v.getSpecificValidationInfo(obj) if err != nil { allErrs = append(allErrs, field.InternalError(nil, err)) return allErrs } allErrs = append(allErrs, specificValidationInfo.Validator.Validate(obj)...) return allErrs } func (v *RuntimeObjectsValidator) ValidateUpdate(obj, old runtime.Object) field.ErrorList { if obj == nil && old == nil { return field.ErrorList{} } if newType, oldType := reflect.TypeOf(obj), reflect.TypeOf(old); newType != oldType { return field.ErrorList{field.Invalid(field.NewPath("kind"), newType.Kind(), validation.NewInvalidTypeError(oldType.Kind(), newType.Kind(), "runtime.Object").Error())} } allErrs := field.ErrorList{} specificValidationInfo, err := v.getSpecificValidationInfo(obj) if err != nil { if fieldErr, ok := err.(*field.Error); ok { allErrs = append(allErrs, fieldErr) } else { allErrs = append(allErrs, field.InternalError(nil, err)) } return allErrs } allErrs = append(allErrs, specificValidationInfo.Validator.ValidateUpdate(obj, old)...) // no errors so far, make sure that the new object is actually valid against the original validator if len(allErrs) == 0 { allErrs = append(allErrs, specificValidationInfo.Validator.Validate(obj)...) } return allErrs } func (v *RuntimeObjectsValidator) getSpecificValidationInfo(obj runtime.Object) (RuntimeObjectValidatorInfo, error) { objType := reflect.TypeOf(obj) specificValidationInfo, exists := v.typeToValidator[objType] if !exists { return RuntimeObjectValidatorInfo{}, fmt.Errorf("no validator registered for %v", objType) } return specificValidationInfo, nil } func GetRequiresNamespace(obj runtime.Object) (bool, error) { groupVersionKinds, _, err := kapi.Scheme.ObjectKinds(obj) if err != nil { return false, err } restMapping, err := kapi.RESTMapper.RESTMapping(groupVersionKinds[0].GroupKind()) if err != nil { return false, err } return restMapping.Scope.Name() == meta.RESTScopeNameNamespace, nil } func HasObjectMeta(obj runtime.Object) bool { objValue := reflect.ValueOf(obj).Elem() field := objValue.FieldByName("ObjectMeta") if !field.IsValid() { return false } return true }