mirror of https://github.com/kubernetes/kops.git
242 lines
6.4 KiB
Go
242 lines
6.4 KiB
Go
/*
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package reflectutils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
var SkipReflection = errors.New("skip this value")
|
|
|
|
type MethodNotFoundError struct {
|
|
Name string
|
|
Target interface{}
|
|
}
|
|
|
|
func (e *MethodNotFoundError) Error() string {
|
|
return fmt.Sprintf("method %s not found on %T", e.Name, e.Target)
|
|
}
|
|
|
|
func IsMethodNotFound(err error) bool {
|
|
_, ok := err.(*MethodNotFoundError)
|
|
return ok
|
|
}
|
|
|
|
// JSONMergeStruct merges src into dest
|
|
// It uses a JSON marshal & unmarshal, so only fields that are JSON-visible will be copied
|
|
func JSONMergeStruct(dest, src interface{}) {
|
|
// Not the most efficient approach, but simple & relatively well defined
|
|
j, err := json.Marshal(src)
|
|
if err != nil {
|
|
klog.Fatalf("error marshaling config: %v", err)
|
|
}
|
|
err = json.Unmarshal(j, dest)
|
|
if err != nil {
|
|
klog.Fatalf("error unmarshaling config: %v", err)
|
|
}
|
|
}
|
|
|
|
// InvokeMethod calls the specified method by reflection
|
|
func InvokeMethod(target interface{}, name string, args ...interface{}) ([]reflect.Value, error) {
|
|
v := reflect.ValueOf(target)
|
|
|
|
method, found := v.Type().MethodByName(name)
|
|
if !found {
|
|
return nil, &MethodNotFoundError{
|
|
Name: name,
|
|
Target: target,
|
|
}
|
|
}
|
|
|
|
var argValues []reflect.Value
|
|
for _, a := range args {
|
|
argValues = append(argValues, reflect.ValueOf(a))
|
|
}
|
|
klog.V(12).Infof("Calling method %s on %T", method.Name, target)
|
|
m := v.MethodByName(method.Name)
|
|
rv := m.Call(argValues)
|
|
return rv, nil
|
|
}
|
|
|
|
func BuildTypeName(t reflect.Type) string {
|
|
switch t.Kind() {
|
|
case reflect.Ptr:
|
|
return "*" + BuildTypeName(t.Elem())
|
|
case reflect.Slice:
|
|
return "[]" + BuildTypeName(t.Elem())
|
|
case reflect.Struct, reflect.Interface:
|
|
return t.Name()
|
|
case reflect.String, reflect.Bool, reflect.Int64, reflect.Uint8:
|
|
return t.Name()
|
|
case reflect.Map:
|
|
return "map[" + BuildTypeName(t.Key()) + "]" + BuildTypeName(t.Elem())
|
|
default:
|
|
klog.Errorf("cannot find type name for: %v, assuming %s", t, t.Name())
|
|
return t.Name()
|
|
}
|
|
}
|
|
|
|
type visitorFunc func(path string, field *reflect.StructField, v reflect.Value) error
|
|
|
|
// ReflectRecursive calls visitor with v and every recursive sub-value, skipping subtrees if SkipReflection is returned
|
|
func ReflectRecursive(v reflect.Value, visitor visitorFunc) error {
|
|
return reflectRecursive("", v, visitor)
|
|
}
|
|
|
|
func reflectRecursive(path string, v reflect.Value, visitor visitorFunc) error {
|
|
vType := v.Type()
|
|
|
|
err := visitor(path, nil, v)
|
|
if err != nil {
|
|
if err == SkipReflection {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Struct:
|
|
for i := 0; i < v.NumField(); i++ {
|
|
structField := vType.Field(i)
|
|
if structField.PkgPath != "" {
|
|
// Field not exported
|
|
continue
|
|
}
|
|
|
|
f := v.Field(i)
|
|
|
|
childPath := path + "." + structField.Name
|
|
// TODO: I think we are double visiting here; we should instead pass down structField into reflectRecursive
|
|
err := visitor(childPath, &structField, f)
|
|
if err != nil && err != SkipReflection {
|
|
return err
|
|
}
|
|
if err == nil {
|
|
err = reflectRecursive(childPath, f, visitor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
case reflect.Map:
|
|
keys := v.MapKeys()
|
|
for _, key := range keys {
|
|
mv := v.MapIndex(key)
|
|
|
|
childPath := path + "[" + fmt.Sprintf("%s", key.Interface()) + "]"
|
|
err := visitor(childPath, nil, mv)
|
|
if err != nil && err != SkipReflection {
|
|
return err
|
|
}
|
|
if err == nil {
|
|
err = reflectRecursive(childPath, mv, visitor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
case reflect.Array, reflect.Slice:
|
|
len := v.Len()
|
|
for i := 0; i < len; i++ {
|
|
av := v.Index(i)
|
|
|
|
childPath := path + "[" + fmt.Sprintf("%d", i) + "]"
|
|
err := visitor(childPath, nil, av)
|
|
if err != nil && err != SkipReflection {
|
|
return err
|
|
}
|
|
if err == nil {
|
|
err = reflectRecursive(childPath, av, visitor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
case reflect.Ptr, reflect.Interface:
|
|
if !v.IsNil() {
|
|
e := v.Elem()
|
|
err = reflectRecursive(path, e, visitor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsPrimitiveValue returns true if passed a value of primitive type: int, bool, etc
|
|
// Note that string (like []byte) is not treated as a primitive type
|
|
func IsPrimitiveValue(v reflect.Value) bool {
|
|
switch v.Kind() {
|
|
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
|
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
|
return true
|
|
|
|
// The less-obvious cases!
|
|
case reflect.String, reflect.Slice, reflect.Array:
|
|
return false
|
|
|
|
case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Struct, reflect.UnsafePointer:
|
|
return false
|
|
|
|
default:
|
|
klog.Fatalf("Unhandled kind: %v", v.Kind())
|
|
return false
|
|
}
|
|
}
|
|
|
|
// FormatValue returns a string representing the value
|
|
func FormatValue(value interface{}) string {
|
|
// Based on code in k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go
|
|
valueType := reflect.TypeOf(value)
|
|
if value == nil || valueType == nil {
|
|
value = "null"
|
|
} else if valueType.Kind() == reflect.Ptr {
|
|
if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() {
|
|
value = "null"
|
|
} else {
|
|
value = reflectValue.Elem().Interface()
|
|
}
|
|
}
|
|
switch t := value.(type) {
|
|
case int64, int32, float64, float32, bool:
|
|
// use simple printer for simple types
|
|
return fmt.Sprintf("%v", value)
|
|
case string:
|
|
return fmt.Sprintf("%q", t)
|
|
case fmt.Stringer:
|
|
// anything that defines String() is better than raw struct
|
|
return t.String()
|
|
default:
|
|
// fallback to raw struct
|
|
// TODO: internal types have panic guards against json.Marshaling to prevent
|
|
// accidental use of internal types in external serialized form. For now, use
|
|
// %#v, although it would be better to show a more expressive output in the future
|
|
return fmt.Sprintf("%#v", value)
|
|
}
|
|
}
|