diff --git a/pkg/flagbuilder/BUILD.bazel b/pkg/flagbuilder/BUILD.bazel index ba95b8b4a3c17..4eb0cfb3429eb 100644 --- a/pkg/flagbuilder/BUILD.bazel +++ b/pkg/flagbuilder/BUILD.bazel @@ -6,7 +6,7 @@ go_library( importpath = "k8s.io/kops/pkg/flagbuilder", visibility = ["//visibility:public"], deps = [ - "//upup/pkg/fi/utils:go_default_library", + "//util/pkg/reflectutils:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], diff --git a/pkg/flagbuilder/build_flags.go b/pkg/flagbuilder/build_flags.go index 60d9097cbae9b..48d06f64d781a 100644 --- a/pkg/flagbuilder/build_flags.go +++ b/pkg/flagbuilder/build_flags.go @@ -24,9 +24,10 @@ import ( "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kops/upup/pkg/fi/utils" "github.com/golang/glog" + + "k8s.io/kops/util/pkg/reflectutils" ) // BuildFlags returns a space separated list arguments @@ -57,7 +58,7 @@ func BuildFlagsList(options interface{}) ([]string, error) { } if tag == "-" { glog.V(4).Infof("skipping field with %q flag tag: %s", tag, path) - return utils.SkipReflection + return reflectutils.SkipReflection } // If we specify the repeat option, we will repeat the flag rather than joining it with commas @@ -109,7 +110,7 @@ func BuildFlagsList(options interface{}) ([]string, error) { flag := fmt.Sprintf("--%s=%s", flagName, strings.Join(args, ",")) flags = append(flags, flag) } - return utils.SkipReflection + return reflectutils.SkipReflection } return fmt.Errorf("BuildFlags of value type not handled: %T %s=%v", val.Interface(), path, val.Interface()) @@ -132,7 +133,7 @@ func BuildFlagsList(options interface{}) ([]string, error) { flags = append(flags, flag) } } - return utils.SkipReflection + return reflectutils.SkipReflection } return fmt.Errorf("BuildFlags of value type not handled: %T %s=%v", val.Interface(), path, val.Interface()) @@ -188,9 +189,9 @@ func BuildFlagsList(options interface{}) ([]string, error) { flags = append(flags, flag) } - return utils.SkipReflection + return reflectutils.SkipReflection } - err := utils.ReflectRecursive(reflect.ValueOf(options), walker) + err := reflectutils.ReflectRecursive(reflect.ValueOf(options), walker) if err != nil { return nil, fmt.Errorf("BuildFlagsList to reflect value: %s", err) } diff --git a/upup/pkg/fi/BUILD.bazel b/upup/pkg/fi/BUILD.bazel index 1bceefe0000bc..f2c69e045d778 100644 --- a/upup/pkg/fi/BUILD.bazel +++ b/upup/pkg/fi/BUILD.bazel @@ -21,6 +21,7 @@ go_library( "http.go", "lifecycle.go", "named.go", + "printers.go", "resources.go", "secrets.go", "target.go", @@ -45,8 +46,10 @@ go_library( "//pkg/kopscodecs:go_default_library", "//pkg/pki:go_default_library", "//pkg/sshcredentials:go_default_library", + "//pkg/values:go_default_library", "//upup/pkg/fi/utils:go_default_library", "//util/pkg/hashing:go_default_library", + "//util/pkg/reflectutils:go_default_library", "//util/pkg/vfs:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/golang.org/x/crypto/ssh:go_default_library", diff --git a/upup/pkg/fi/cloudup/BUILD.bazel b/upup/pkg/fi/cloudup/BUILD.bazel index ae2f1e04e484b..5699f2b8f75b3 100644 --- a/upup/pkg/fi/cloudup/BUILD.bazel +++ b/upup/pkg/fi/cloudup/BUILD.bazel @@ -73,6 +73,7 @@ go_library( "//upup/pkg/fi/loader:go_default_library", "//upup/pkg/fi/utils:go_default_library", "//util/pkg/hashing:go_default_library", + "//util/pkg/reflectutils:go_default_library", "//util/pkg/vfs:go_default_library", "//vendor/github.com/blang/semver:go_default_library", "//vendor/github.com/golang/glog:go_default_library", diff --git a/upup/pkg/fi/cloudup/loader.go b/upup/pkg/fi/cloudup/loader.go index 7e097c2eea253..3bccd262a191e 100644 --- a/upup/pkg/fi/cloudup/loader.go +++ b/upup/pkg/fi/cloudup/loader.go @@ -34,6 +34,7 @@ import ( "k8s.io/kops/upup/pkg/fi/assettasks" "k8s.io/kops/upup/pkg/fi/loader" "k8s.io/kops/upup/pkg/fi/utils" + "k8s.io/kops/util/pkg/reflectutils" "k8s.io/kops/util/pkg/vfs" ) @@ -260,8 +261,8 @@ func (l *Loader) processDeferrals() error { for taskKey, task := range l.tasks { taskValue := reflect.ValueOf(task) - err := utils.ReflectRecursive(taskValue, func(path string, f *reflect.StructField, v reflect.Value) error { - if utils.IsPrimitiveValue(v) { + err := reflectutils.ReflectRecursive(taskValue, func(path string, f *reflect.StructField, v reflect.Value) error { + if reflectutils.IsPrimitiveValue(v) { return nil } @@ -299,7 +300,7 @@ func (l *Loader) processDeferrals() error { glog.V(11).Infof("Replacing task %q at %s:%s", *name, taskKey, path) v.Set(reflect.ValueOf(primary)) } - return utils.SkipReflection + return reflectutils.SkipReflection } else if rh, ok := intf.(*fi.ResourceHolder); ok { if rh.Resource == nil { //Resources can contain template 'arguments', separated by spaces @@ -324,7 +325,7 @@ func (l *Loader) processDeferrals() error { return fmt.Errorf("error setting resource value: %v", err) } } - return utils.SkipReflection + return reflectutils.SkipReflection } } } diff --git a/upup/pkg/fi/cloudup/populate_cluster_spec.go b/upup/pkg/fi/cloudup/populate_cluster_spec.go index 7b6dcf4d94700..24f8249aa950d 100644 --- a/upup/pkg/fi/cloudup/populate_cluster_spec.go +++ b/upup/pkg/fi/cloudup/populate_cluster_spec.go @@ -38,7 +38,7 @@ import ( "k8s.io/kops/upup/models" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/loader" - "k8s.io/kops/upup/pkg/fi/utils" + "k8s.io/kops/util/pkg/reflectutils" "k8s.io/kops/util/pkg/vfs" ) @@ -94,7 +94,7 @@ func (c *populateClusterSpec) run(clientset simple.Clientset) error { // Copy cluster & instance groups, so we can modify them freely cluster := &api.Cluster{} - utils.JsonMergeStruct(cluster, c.InputCluster) + reflectutils.JsonMergeStruct(cluster, c.InputCluster) err := c.assignSubnets(cluster) if err != nil { diff --git a/upup/pkg/fi/cloudup/populate_instancegroup_spec.go b/upup/pkg/fi/cloudup/populate_instancegroup_spec.go index 811635af1a40e..18321090e1860 100644 --- a/upup/pkg/fi/cloudup/populate_instancegroup_spec.go +++ b/upup/pkg/fi/cloudup/populate_instancegroup_spec.go @@ -26,7 +26,7 @@ import ( "k8s.io/kops/pkg/apis/kops/validation" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awsup" - "k8s.io/kops/upup/pkg/fi/utils" + "k8s.io/kops/util/pkg/reflectutils" ) // Default Machine types for various types of instance group machine @@ -64,7 +64,7 @@ func PopulateInstanceGroupSpec(cluster *kops.Cluster, input *kops.InstanceGroup, } ig := &kops.InstanceGroup{} - utils.JsonMergeStruct(ig, input) + reflectutils.JsonMergeStruct(ig, input) // TODO: Clean up if ig.IsMaster() { diff --git a/upup/pkg/fi/cloudup/spec_builder.go b/upup/pkg/fi/cloudup/spec_builder.go index 07d9236b80dfb..3b4284a9aeaac 100644 --- a/upup/pkg/fi/cloudup/spec_builder.go +++ b/upup/pkg/fi/cloudup/spec_builder.go @@ -22,7 +22,7 @@ import ( api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/loader" - "k8s.io/kops/upup/pkg/fi/utils" + "k8s.io/kops/util/pkg/reflectutils" ) type SpecBuilder struct { @@ -42,8 +42,8 @@ func (l *SpecBuilder) BuildCompleteSpec(clusterSpec *api.ClusterSpec) (*api.Clus // Master kubelet config = (base kubelet config + master kubelet config) masterKubelet := &api.KubeletConfigSpec{} - utils.JsonMergeStruct(masterKubelet, completed.Kubelet) - utils.JsonMergeStruct(masterKubelet, completed.MasterKubelet) + reflectutils.JsonMergeStruct(masterKubelet, completed.Kubelet) + reflectutils.JsonMergeStruct(masterKubelet, completed.MasterKubelet) completed.MasterKubelet = masterKubelet glog.V(1).Infof("options: %s", fi.DebugAsJsonStringIndent(completed)) diff --git a/upup/pkg/fi/default_methods.go b/upup/pkg/fi/default_methods.go index b516c87aa1314..64dbb9930e4ab 100644 --- a/upup/pkg/fi/default_methods.go +++ b/upup/pkg/fi/default_methods.go @@ -20,7 +20,7 @@ import ( "fmt" "reflect" - "k8s.io/kops/upup/pkg/fi/utils" + "k8s.io/kops/util/pkg/reflectutils" ) // DefaultDeltaRunMethod implements the standard change-based run procedure: @@ -108,7 +108,7 @@ func DefaultDeltaRunMethod(e Task, c *Context) error { // invokeCheckChanges calls the checkChanges method by reflection func invokeCheckChanges(a, e, changes Task) error { - rv, err := utils.InvokeMethod(e, "CheckChanges", a, e, changes) + rv, err := reflectutils.InvokeMethod(e, "CheckChanges", a, e, changes) if err != nil { return err } @@ -120,7 +120,7 @@ func invokeCheckChanges(a, e, changes Task) error { // invokeFind calls the find method by reflection func invokeFind(e Task, c *Context) (Task, error) { - rv, err := utils.InvokeMethod(e, "Find", c) + rv, err := reflectutils.InvokeMethod(e, "Find", c) if err != nil { return nil, err } @@ -136,9 +136,9 @@ func invokeFind(e Task, c *Context) (Task, error) { // invokeShouldCreate calls the ShouldCreate method by reflection, if it exists func invokeShouldCreate(a, e, changes Task) (bool, error) { - rv, err := utils.InvokeMethod(e, "ShouldCreate", a, e, changes) + rv, err := reflectutils.InvokeMethod(e, "ShouldCreate", a, e, changes) if err != nil { - if utils.IsMethodNotFound(err) { + if reflectutils.IsMethodNotFound(err) { return true, nil } return false, err diff --git a/upup/pkg/fi/dryrun_target.go b/upup/pkg/fi/dryrun_target.go index 73a7cd838fc18..33baa76c15cac 100644 --- a/upup/pkg/fi/dryrun_target.go +++ b/upup/pkg/fi/dryrun_target.go @@ -28,7 +28,7 @@ import ( "github.com/golang/glog" "k8s.io/kops/pkg/assets" "k8s.io/kops/pkg/diff" - "k8s.io/kops/upup/pkg/fi/utils" + "k8s.io/kops/util/pkg/reflectutils" ) // DryRunTarget is a special Target that does not execute anything, but instead tracks all changes. @@ -167,7 +167,7 @@ func (t *DryRunTarget) PrintReport(taskMap map[string]Task, out io.Writer) error continue } - fieldValue := ValueAsString(field) + fieldValue := reflectutils.ValueAsString(field) shouldPrint := true if fieldName == "Name" { @@ -338,7 +338,7 @@ func buildChangeList(a, e, changes Task) ([]change, error) { } if !ignored && description == "" { - description = fmt.Sprintf(" %v -> %v", ValueAsString(fieldValA), ValueAsString(fieldValE)) + description = fmt.Sprintf(" %v -> %v", reflectutils.ValueAsString(fieldValA), reflectutils.ValueAsString(fieldValE)) } } if ignored { @@ -398,104 +398,6 @@ func getTaskName(t Task) string { return s } -// ValueAsString returns a human-readable string representation of the passed value -func ValueAsString(value reflect.Value) string { - b := &bytes.Buffer{} - - walker := func(path string, field *reflect.StructField, v reflect.Value) error { - if utils.IsPrimitiveValue(v) || v.Kind() == reflect.String { - fmt.Fprintf(b, "%v", v.Interface()) - return utils.SkipReflection - } - - switch v.Kind() { - case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map: - if v.IsNil() { - fmt.Fprintf(b, "") - return utils.SkipReflection - } - } - - switch v.Kind() { - case reflect.Ptr, reflect.Interface: - return nil // descend into value - - case reflect.Slice: - len := v.Len() - fmt.Fprintf(b, "[") - for i := 0; i < len; i++ { - av := v.Index(i) - - if i != 0 { - fmt.Fprintf(b, ", ") - } - fmt.Fprintf(b, "%s", ValueAsString(av)) - } - fmt.Fprintf(b, "]") - return utils.SkipReflection - - case reflect.Map: - keys := v.MapKeys() - fmt.Fprintf(b, "{") - for i, key := range keys { - mv := v.MapIndex(key) - - if i != 0 { - fmt.Fprintf(b, ", ") - } - fmt.Fprintf(b, "%s: %s", ValueAsString(key), ValueAsString(mv)) - } - fmt.Fprintf(b, "}") - return utils.SkipReflection - - case reflect.Struct: - intf := v.Addr().Interface() - if _, ok := intf.(Resource); ok { - fmt.Fprintf(b, "") - } else if _, ok := intf.(*ResourceHolder); ok { - fmt.Fprintf(b, "") - } else if compareWithID, ok := intf.(CompareWithID); ok { - id := compareWithID.CompareWithID() - name := "" - hasName, ok := intf.(HasName) - if ok { - name = StringValue(hasName.GetName()) - } - if id == nil { - // Uninformative, but we can often print the name instead - if name != "" { - fmt.Fprintf(b, "name:%s", name) - } else { - fmt.Fprintf(b, "id:") - } - } else { - // Uninformative, but we can often print the name instead - if name != "" { - fmt.Fprintf(b, "name:%s id:%s", name, *id) - } else { - fmt.Fprintf(b, "id:%s", *id) - } - - } - } else { - glog.V(4).Infof("Unhandled kind in asString for %q: %T", path, v.Interface()) - fmt.Fprint(b, DebugAsJsonString(intf)) - } - return utils.SkipReflection - - default: - glog.Infof("Unhandled kind in asString for %q: %T", path, v.Interface()) - return fmt.Errorf("Unhandled kind for %q: %v", path, v.Kind()) - } - } - - err := utils.ReflectRecursive(value, walker) - if err != nil { - glog.Fatalf("unexpected error during reflective walk: %v", err) - } - return b.String() -} - // Finish is called at the end of a run, and prints a list of changes to the configured Writer func (t *DryRunTarget) Finish(taskMap map[string]Task) error { return t.PrintReport(taskMap, t.out) diff --git a/upup/pkg/fi/errors.go b/upup/pkg/fi/errors.go index ce5257cc9f070..b224be45f14ee 100644 --- a/upup/pkg/fi/errors.go +++ b/upup/pkg/fi/errors.go @@ -21,7 +21,8 @@ import ( "k8s.io/apimachinery/pkg/api/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kops/upup/pkg/fi/utils" + + "k8s.io/kops/util/pkg/reflectutils" ) func RequiredField(key string) error { @@ -33,6 +34,6 @@ func CannotChangeField(key string) error { } func FieldIsImmutable(newVal, oldVal interface{}, fldPath *field.Path) *field.Error { - details := fmt.Sprintf("%s: old=%v new=%v", validation.FieldImmutableErrorMsg, utils.FormatValue(oldVal), utils.FormatValue(newVal)) + details := fmt.Sprintf("%s: old=%v new=%v", validation.FieldImmutableErrorMsg, reflectutils.FormatValue(oldVal), reflectutils.FormatValue(newVal)) return field.Invalid(fldPath, newVal, details) } diff --git a/upup/pkg/fi/loader/BUILD.bazel b/upup/pkg/fi/loader/BUILD.bazel index c8060e596220d..9bb95a99b9244 100644 --- a/upup/pkg/fi/loader/BUILD.bazel +++ b/upup/pkg/fi/loader/BUILD.bazel @@ -10,6 +10,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//upup/pkg/fi/utils:go_default_library", + "//util/pkg/reflectutils:go_default_library", "//util/pkg/vfs:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/upup/pkg/fi/loader/options_loader.go b/upup/pkg/fi/loader/options_loader.go index c41b3520e2777..90ee20dd52154 100644 --- a/upup/pkg/fi/loader/options_loader.go +++ b/upup/pkg/fi/loader/options_loader.go @@ -28,6 +28,7 @@ import ( "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi/utils" + "k8s.io/kops/util/pkg/reflectutils" ) const maxIterations = 10 @@ -96,7 +97,7 @@ func (l *OptionsLoader) iterate(userConfig interface{}, current interface{}) (in next := reflect.New(t).Interface() // Copy the current state before applying rules; they act as defaults - utils.JsonMergeStruct(next, current) + reflectutils.JsonMergeStruct(next, current) for _, t := range l.templates { glog.V(2).Infof("executing template %s (tags=%s)", t.Name, t.Tags) @@ -135,7 +136,7 @@ func (l *OptionsLoader) iterate(userConfig interface{}, current interface{}) (in } // Also copy the user-provided values after applying rules; they act as overrides now - utils.JsonMergeStruct(next, userConfig) + reflectutils.JsonMergeStruct(next, userConfig) return next, nil } diff --git a/upup/pkg/fi/printers.go b/upup/pkg/fi/printers.go new file mode 100644 index 0000000000000..1cd77292828d4 --- /dev/null +++ b/upup/pkg/fi/printers.go @@ -0,0 +1,75 @@ +/* +Copyright 2018 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 fi + +import ( + "fmt" + + "k8s.io/kops/pkg/values" + "k8s.io/kops/util/pkg/reflectutils" +) + +func init() { + // Register our custom printer functions + reflectutils.RegisterPrinter(PrintResource) + reflectutils.RegisterPrinter(PrintResourceHolder) + reflectutils.RegisterPrinter(PrintCompareWithID) +} + +func PrintResource(o interface{}) (string, bool) { + if _, ok := o.(Resource); !ok { + return "", false + } + return "", true +} + +func PrintResourceHolder(o interface{}) (string, bool) { + if _, ok := o.(*ResourceHolder); !ok { + return "", false + } + return "", true +} + +func PrintCompareWithID(o interface{}) (string, bool) { + compareWithID, ok := o.(CompareWithID) + if !ok { + return "", false + } + + id := compareWithID.CompareWithID() + name := "" + hasName, ok := o.(HasName) + if ok { + name = values.StringValue(hasName.GetName()) + } + if id == nil { + // Uninformative, but we can often print the name instead + if name != "" { + return fmt.Sprintf("name:%s", name), true + } else { + return "id:", true + } + } else { + // Uninformative, but we can often print the name instead + if name != "" { + return fmt.Sprintf("name:%s id:%s", name, *id), true + } else { + return fmt.Sprintf("id:%s", *id), true + } + + } +} diff --git a/upup/pkg/fi/topological_sort.go b/upup/pkg/fi/topological_sort.go index 2dca6869411c9..f3c489a3f1aa0 100644 --- a/upup/pkg/fi/topological_sort.go +++ b/upup/pkg/fi/topological_sort.go @@ -22,7 +22,8 @@ import ( "reflect" "github.com/golang/glog" - "k8s.io/kops/upup/pkg/fi/utils" + + "k8s.io/kops/util/pkg/reflectutils" ) type HasDependencies interface { @@ -76,8 +77,8 @@ func reflectForDependencies(tasks map[string]Task, task Task) []Task { func getDependencies(tasks map[string]Task, v reflect.Value) []Task { var dependencies []Task - err := utils.ReflectRecursive(v, func(path string, f *reflect.StructField, v reflect.Value) error { - if utils.IsPrimitiveValue(v) { + err := reflectutils.ReflectRecursive(v, func(path string, f *reflect.StructField, v reflect.Value) error { + if reflectutils.IsPrimitiveValue(v) { return nil } @@ -111,7 +112,7 @@ func getDependencies(tasks map[string]Task, v reflect.Value) []Task { } else { return fmt.Errorf("Unhandled type for %q: %T", path, v.Interface()) } - return utils.SkipReflection + return reflectutils.SkipReflection default: glog.Infof("Unhandled kind for %q: %T", path, v.Interface()) diff --git a/upup/pkg/fi/utils/BUILD.bazel b/upup/pkg/fi/utils/BUILD.bazel index ad570a7a3a082..17bcf9af4de66 100644 --- a/upup/pkg/fi/utils/BUILD.bazel +++ b/upup/pkg/fi/utils/BUILD.bazel @@ -4,7 +4,6 @@ go_library( name = "go_default_library", srcs = [ "equals.go", - "reflect.go", "sanitize.go", "yaml.go", ], @@ -12,7 +11,6 @@ go_library( visibility = ["//visibility:public"], deps = [ "//vendor/github.com/ghodss/yaml:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/client-go/util/homedir:go_default_library", ], ) diff --git a/util/pkg/reflectutils/BUILD.bazel b/util/pkg/reflectutils/BUILD.bazel new file mode 100644 index 0000000000000..c500369335b5f --- /dev/null +++ b/util/pkg/reflectutils/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "print.go", + "walk.go", + ], + importpath = "k8s.io/kops/util/pkg/reflectutils", + visibility = ["//visibility:public"], + deps = [ + "//pkg/values:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + ], +) diff --git a/util/pkg/reflectutils/print.go b/util/pkg/reflectutils/print.go new file mode 100644 index 0000000000000..263cf28151a9f --- /dev/null +++ b/util/pkg/reflectutils/print.go @@ -0,0 +1,121 @@ +/* +Copyright 2016 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 ( + "bytes" + "fmt" + "reflect" + + "github.com/golang/glog" + + "k8s.io/kops/pkg/values" +) + +// Printer is a custom printer function, so we can add special display for objects +// (without introducing a package dependency) +type Printer func(o interface{}) (string, bool) + +var printers []Printer + +// RegisterPrinter adds a custom printer function +func RegisterPrinter(p Printer) { + printers = append(printers, p) +} + +// ValueAsString returns a human-readable string representation of the passed value +func ValueAsString(value reflect.Value) string { + b := &bytes.Buffer{} + + walker := func(path string, field *reflect.StructField, v reflect.Value) error { + if IsPrimitiveValue(v) || v.Kind() == reflect.String { + fmt.Fprintf(b, "%v", v.Interface()) + return SkipReflection + } + + switch v.Kind() { + case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map: + if v.IsNil() { + fmt.Fprintf(b, "") + return SkipReflection + } + } + + switch v.Kind() { + case reflect.Ptr, reflect.Interface: + return nil // descend into value + + case reflect.Slice: + len := v.Len() + fmt.Fprintf(b, "[") + for i := 0; i < len; i++ { + av := v.Index(i) + + if i != 0 { + fmt.Fprintf(b, ", ") + } + fmt.Fprintf(b, "%s", ValueAsString(av)) + } + fmt.Fprintf(b, "]") + return SkipReflection + + case reflect.Map: + keys := v.MapKeys() + fmt.Fprintf(b, "{") + for i, key := range keys { + mv := v.MapIndex(key) + + if i != 0 { + fmt.Fprintf(b, ", ") + } + fmt.Fprintf(b, "%s: %s", ValueAsString(key), ValueAsString(mv)) + } + fmt.Fprintf(b, "}") + return SkipReflection + + case reflect.Struct: + intf := v.Addr().Interface() + + done := false + for _, p := range printers { + s, ok := p(intf) + if ok { + fmt.Fprintf(b, s) + done = true + break + } + } + + if !done { + glog.V(4).Infof("Unhandled kind in asString for %q: %T", path, v.Interface()) + fmt.Fprint(b, values.DebugAsJsonString(intf)) + } + + return SkipReflection + + default: + glog.Infof("Unhandled kind in asString for %q: %T", path, v.Interface()) + return fmt.Errorf("Unhandled kind for %q: %v", path, v.Kind()) + } + } + + err := ReflectRecursive(value, walker) + if err != nil { + glog.Fatalf("unexpected error during reflective walk: %v", err) + } + return b.String() +} diff --git a/upup/pkg/fi/utils/reflect.go b/util/pkg/reflectutils/walk.go similarity index 99% rename from upup/pkg/fi/utils/reflect.go rename to util/pkg/reflectutils/walk.go index a753ac00fd98c..f656bfa5d8af9 100644 --- a/upup/pkg/fi/utils/reflect.go +++ b/util/pkg/reflectutils/walk.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package reflectutils import ( "encoding/json" diff --git a/util/pkg/tables/BUILD.bazel b/util/pkg/tables/BUILD.bazel index 7f2621fc8ef01..d3e4bdedcda78 100644 --- a/util/pkg/tables/BUILD.bazel +++ b/util/pkg/tables/BUILD.bazel @@ -6,7 +6,7 @@ go_library( importpath = "k8s.io/kops/util/pkg/tables", visibility = ["//visibility:public"], deps = [ - "//upup/pkg/fi:go_default_library", + "//util/pkg/reflectutils:go_default_library", "//vendor/github.com/golang/glog:go_default_library", ], ) diff --git a/util/pkg/tables/format.go b/util/pkg/tables/format.go index e800c87f190fd..614c790ba4ede 100644 --- a/util/pkg/tables/format.go +++ b/util/pkg/tables/format.go @@ -25,7 +25,8 @@ import ( "text/tabwriter" "github.com/golang/glog" - "k8s.io/kops/upup/pkg/fi" + + "k8s.io/kops/util/pkg/reflectutils" ) // Table renders tables to stdout @@ -44,7 +45,7 @@ func (c *TableColumn) getFromValue(v reflect.Value) string { fvs := c.Getter.Call(args) fv := fvs[0] - return fi.ValueAsString(fv) + return reflectutils.ValueAsString(fv) } type getterFunction func(interface{}) string