mirror of https://github.com/kubernetes/kops.git
272 lines
7.4 KiB
Go
272 lines
7.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"
|
|
"strings"
|
|
|
|
"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
|
|
// If both source and destination has a value for a field, source takes presedence
|
|
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 *FieldPath, field *reflect.StructField, v reflect.Value) error
|
|
|
|
type ReflectOptions struct {
|
|
// JSONNames means the elements of the FieldPath will be the JSON field names (instead of the go field names)
|
|
JSONNames bool
|
|
|
|
// DeprecatedDoubleVisit activates the compatibility mode of the walker, where we end up visiting every struct field twice.
|
|
// Ideally we can replace these instances over time.
|
|
DeprecatedDoubleVisit bool
|
|
}
|
|
|
|
// ReflectRecursive calls visitor with v and every recursive sub-value, skipping subtrees if SkipReflection is returned
|
|
func ReflectRecursive(v reflect.Value, visitor visitorFunc, options *ReflectOptions) error {
|
|
emptyFieldPath := FieldPath{}
|
|
|
|
return reflectRecursive(&emptyFieldPath, v, visitor, options)
|
|
}
|
|
|
|
func reflectRecursive(path *FieldPath, v reflect.Value, visitor visitorFunc, options *ReflectOptions) 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)
|
|
|
|
fieldName := structField.Name
|
|
|
|
if options.JSONNames {
|
|
jsonTag, ok := structField.Tag.Lookup("json")
|
|
if ok {
|
|
v := strings.Split(jsonTag, ",")[0]
|
|
if v != "" {
|
|
fieldName = v
|
|
}
|
|
}
|
|
}
|
|
childPath := path.Extend(FieldPathElement{Type: FieldPathElementTypeField, token: fieldName})
|
|
|
|
var err error
|
|
if options.DeprecatedDoubleVisit {
|
|
// The legacy behaviour here involves visiting fields twice; only as the StructField, once as the value itself
|
|
err = visitor(childPath, &structField, f)
|
|
if err != nil && err != SkipReflection {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err == nil {
|
|
err = reflectRecursive(childPath, f, visitor, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
case reflect.Map:
|
|
keys := v.MapKeys()
|
|
for _, key := range keys {
|
|
mv := v.MapIndex(key)
|
|
|
|
childPath := path.Extend(FieldPathElement{Type: FieldPathElementTypeMapKey, token: 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, options)
|
|
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.Extend(FieldPathElement{Type: FieldPathElementTypeArrayIndex, number: i})
|
|
err := visitor(childPath, nil, av)
|
|
if err != nil && err != SkipReflection {
|
|
return err
|
|
}
|
|
if err == nil {
|
|
err = reflectRecursive(childPath, av, visitor, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
case reflect.Ptr, reflect.Interface:
|
|
if !v.IsNil() {
|
|
e := v.Elem()
|
|
err = reflectRecursive(path, e, visitor, options)
|
|
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)
|
|
}
|
|
}
|