Smarter get printer for generic resources

This commit is contained in:
Fabiano Franz 2017-04-07 16:57:28 -03:00
parent 151770c8fd
commit b276f17b6d
3 changed files with 163 additions and 7 deletions

View File

@ -25,6 +25,8 @@ go_library(
],
tags = ["automanaged"],
deps = [
"//pkg/util/slice:go_default_library",
"//vendor/github.com/fatih/camelcase:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
@ -64,3 +66,11 @@ filegroup(
],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["humanreadable_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library"],
)

View File

@ -21,15 +21,18 @@ import (
"fmt"
"io"
"reflect"
"sort"
"strings"
"text/tabwriter"
"github.com/fatih/camelcase"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/util/slice"
)
var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
@ -201,8 +204,51 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
}
if _, err := meta.Accessor(obj); err == nil {
// we don't recognize this type, but we can still attempt to print some reasonable information about.
unstructured, ok := obj.(runtime.Unstructured)
if !ok {
return fmt.Errorf("error: unknown type %#v", obj)
}
content := unstructured.UnstructuredContent()
// we'll elect a few more fields to print depending on how much columns are already taken
maxDiscoveredFieldsToPrint := 3
maxDiscoveredFieldsToPrint = maxDiscoveredFieldsToPrint - len(h.options.ColumnLabels)
if h.options.WithNamespace { // where's my ternary
maxDiscoveredFieldsToPrint--
}
if h.options.ShowLabels {
maxDiscoveredFieldsToPrint--
}
if maxDiscoveredFieldsToPrint < 0 {
maxDiscoveredFieldsToPrint = 0
}
var discoveredFieldNames []string // we want it predictable so this will be used to sort
ignoreIfDiscovered := []string{"kind", "apiVersion"} // these are already covered
for field, value := range content {
if slice.ContainsString(ignoreIfDiscovered, field, nil) {
continue
}
switch value.(type) {
case map[string]interface{}:
// just simpler types
continue
}
discoveredFieldNames = append(discoveredFieldNames, field)
}
sort.Strings(discoveredFieldNames)
if len(discoveredFieldNames) > maxDiscoveredFieldsToPrint {
discoveredFieldNames = discoveredFieldNames[:maxDiscoveredFieldsToPrint]
}
if !h.options.NoHeaders && t != h.lastType {
headers := []string{"NAME", "KIND"}
for _, discoveredField := range discoveredFieldNames {
fieldAsHeader := strings.ToUpper(strings.Join(camelcase.Split(discoveredField), " "))
headers = append(headers, fieldAsHeader)
}
headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...)
// LABELS is always the last column.
headers = append(headers, formatShowLabelsHeader(h.options.ShowLabels, t)...)
@ -213,13 +259,8 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
h.lastType = t
}
// we don't recognize this type, but we can still attempt to print some reasonable information about.
unstructured, ok := obj.(runtime.Unstructured)
if !ok {
return fmt.Errorf("error: unknown type %#v", obj)
}
// if the error isn't nil, report the "I don't recognize this" error
if err := printUnstructured(unstructured, w, h.options); err != nil {
if err := printUnstructured(unstructured, w, discoveredFieldNames, h.options); err != nil {
return err
}
return nil
@ -230,7 +271,7 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
}
// TODO: this method assumes the meta/v1 server API, so should be refactored out of this package
func printUnstructured(unstructured runtime.Unstructured, w io.Writer, options PrintOptions) error {
func printUnstructured(unstructured runtime.Unstructured, w io.Writer, additionalFields []string, options PrintOptions) error {
metadata, err := meta.Accessor(unstructured)
if err != nil {
return err
@ -258,11 +299,26 @@ func printUnstructured(unstructured runtime.Unstructured, w io.Writer, options P
kind = kind + "." + version.Version + "." + version.Group
}
}
name := formatResourceName(options.Kind, metadata.GetName(), options.WithKind)
if _, err := fmt.Fprintf(w, "%s\t%s", name, kind); err != nil {
return err
}
for _, field := range additionalFields {
if value, ok := content[field]; ok {
var formattedValue string
switch typedValue := value.(type) {
case []interface{}:
formattedValue = fmt.Sprintf("%d item(s)", len(typedValue))
default:
formattedValue = fmt.Sprintf("%v", value)
}
if _, err := fmt.Fprintf(w, "\t%s", formattedValue); err != nil {
return err
}
}
}
if _, err := fmt.Fprint(w, appendLabels(metadata.GetLabels(), options.ColumnLabels)); err != nil {
return err
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2017 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 printers
import (
"bytes"
"regexp"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestPrintUnstructuredObject(t *testing.T) {
tests := []struct {
expected string
options PrintOptions
}{
{
expected: "NAME\\s+KIND\\s+DUMMY 1\\s+DUMMY 2\\s+ITEMS\nMyName\\s+Test\\.v1\\.\\s+present\\s+present\\s+1 item\\(s\\)",
},
{
options: PrintOptions{
WithNamespace: true,
},
expected: "NAMESPACE\\s+NAME\\s+KIND\\s+DUMMY 1\\s+DUMMY 2\nMyNamespace\\s+MyName\\s+Test\\.v1\\.\\s+present\\s+present",
},
{
options: PrintOptions{
ShowLabels: true,
WithNamespace: true,
},
expected: "NAMESPACE\\s+NAME\\s+KIND\\s+DUMMY 1\\s+LABELS\nMyNamespace\\s+MyName\\s+Test\\.v1\\.\\s+present\\s+<none>",
},
}
out := bytes.NewBuffer([]byte{})
obj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Test",
"dummy1": "present",
"dummy2": "present",
"metadata": map[string]interface{}{
"name": "MyName",
"namespace": "MyNamespace",
"creationTimestamp": "2017-04-01T00:00:00Z",
"resourceVersion": 123,
"uid": "00000000-0000-0000-0000-000000000001",
"dummy3": "present",
},
"items": []interface{}{
map[string]interface{}{
"itemBool": true,
"itemInt": 42,
},
},
"url": "http://localhost",
"status": "ok",
},
}
for _, test := range tests {
printer := &HumanReadablePrinter{
options: test.options,
}
printer.PrintObj(obj, out)
matches, err := regexp.MatchString(test.expected, out.String())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !matches {
t.Errorf("wanted %s, got %s", test.expected, out)
}
}
}