Smarter describer for generic resources

pull/6/head
Fabiano Franz 2017-04-06 21:14:16 -03:00
parent 85bd965219
commit 151770c8fd
4 changed files with 166 additions and 0 deletions

View File

@ -87,6 +87,8 @@ go_library(
"//pkg/kubelet/qos:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/slice:go_default_library",
"//vendor/github.com/fatih/camelcase:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",

View File

@ -31,6 +31,7 @@ import (
"github.com/golang/glog"
"github.com/fatih/camelcase"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
@ -70,6 +71,7 @@ import (
"k8s.io/kubernetes/pkg/fieldpath"
"k8s.io/kubernetes/pkg/kubelet/qos"
"k8s.io/kubernetes/pkg/printers"
"k8s.io/kubernetes/pkg/util/slice"
)
// Each level has 2 spaces for PrefixWriter
@ -198,6 +200,8 @@ func (g *genericDescriber) Describe(namespace, name string, describerSettings pr
w.Write(LEVEL_0, "Name:\t%s\n", obj.GetName())
w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace())
printLabelsMultiline(w, "Labels", obj.GetLabels())
printAnnotationsMultiline(w, "Annotations", obj.GetAnnotations())
printUnstructuredContent(w, LEVEL_0, obj.UnstructuredContent(), "", ".metadata.name", ".metadata.namespace", ".metadata.labels", ".metadata.annotations")
if events != nil {
DescribeEvents(events, w)
}
@ -205,6 +209,67 @@ func (g *genericDescriber) Describe(namespace, name string, describerSettings pr
})
}
func printUnstructuredContent(w PrefixWriter, level int, content map[string]interface{}, skipPrefix string, skip ...string) {
fields := []string{}
for field := range content {
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
value := content[field]
switch typedValue := value.(type) {
case map[string]interface{}:
skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field)
if slice.ContainsString(skip, skipExpr, nil) {
continue
}
w.Write(level, fmt.Sprintf("%s:\n", smartLabelFor(field)))
printUnstructuredContent(w, level+1, typedValue, skipExpr, skip...)
case []interface{}:
skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field)
if slice.ContainsString(skip, skipExpr, nil) {
continue
}
w.Write(level, fmt.Sprintf("%s:\n", smartLabelFor(field)))
for _, child := range typedValue {
switch typedChild := child.(type) {
case map[string]interface{}:
printUnstructuredContent(w, level+1, typedChild, skipExpr, skip...)
default:
w.Write(level+1, fmt.Sprintf("%v\n", typedChild))
}
}
default:
skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field)
if slice.ContainsString(skip, skipExpr, nil) {
continue
}
w.Write(level, fmt.Sprintf("%s:\t%v\n", smartLabelFor(field), typedValue))
}
}
}
func smartLabelFor(field string) string {
commonAcronyms := []string{"API", "URL", "UID", "OSB", "GUID"}
splitted := camelcase.Split(field)
for i := 0; i < len(splitted); i++ {
part := splitted[i]
if slice.ContainsString(commonAcronyms, strings.ToUpper(part), nil) {
part = strings.ToUpper(part)
} else {
part = strings.Title(part)
}
splitted[i] = part
}
return strings.Join(splitted, " ")
}
// DefaultObjectDescriber can describe the default Kubernetes objects.
var DefaultObjectDescriber printers.ObjectDescriber

View File

@ -27,6 +27,7 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/federation/apis/federation"
fedfake "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset/fake"
@ -1313,3 +1314,87 @@ func TestPrintLabelsMultiline(t *testing.T) {
}
}
}
func TestDescribeUnstructuredContent(t *testing.T) {
testCases := []struct {
expected string
unexpected string
}{
{
expected: `API Version: v1
Dummy 2: present
Items:
Item Bool: true
Item Int: 42
Kind: Test
Metadata:
Creation Timestamp: 2017-04-01T00:00:00Z
Name: MyName
Namespace: MyNamespace
Resource Version: 123
UID: 00000000-0000-0000-0000-000000000001
Status: ok
URL: http://localhost
`,
},
{
unexpected: "\nDummy 1:\tpresent\n",
},
{
unexpected: "Dummy 1",
},
{
unexpected: "Dummy 3",
},
{
unexpected: "Dummy3",
},
{
unexpected: "dummy3",
},
{
unexpected: "dummy 3",
},
}
out := new(bytes.Buffer)
w := NewPrefixWriter(out)
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",
},
}
printUnstructuredContent(w, LEVEL_0, obj.UnstructuredContent(), "", ".dummy1", ".metadata.dummy3")
output := out.String()
for _, test := range testCases {
if len(test.expected) > 0 {
if !strings.Contains(output, test.expected) {
t.Errorf("Expected to find %q in: %q", test.expected, output)
}
}
if len(test.unexpected) > 0 {
if strings.Contains(output, test.unexpected) {
t.Errorf("Didn't expect to find %q in: %q", test.unexpected, output)
}
}
}
}

View File

@ -49,6 +49,20 @@ func ShuffleStrings(s []string) []string {
return shuffled
}
// ContainsString checks if a given slice of strings contains the provided string.
// If a modifier func is provided, it is called with the slice item before the comparation.
func ContainsString(slice []string, s string, modifier func(s string) string) bool {
for _, item := range slice {
if item == s {
return true
}
if modifier != nil && modifier(item) == s {
return true
}
}
return false
}
// Int64Slice attaches the methods of Interface to []int64,
// sorting in increasing order.
type Int64Slice []int64