mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Smarter get printer for generic resources
This commit is contained in:
parent
151770c8fd
commit
b276f17b6d
@ -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"],
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
90
pkg/printers/humanreadable_test.go
Normal file
90
pkg/printers/humanreadable_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user