mirror of https://github.com/k3s-io/k3s
Smarter describer for generic resources
parent
85bd965219
commit
151770c8fd
|
@ -87,6 +87,8 @@ go_library(
|
||||||
"//pkg/kubelet/qos:go_default_library",
|
"//pkg/kubelet/qos:go_default_library",
|
||||||
"//pkg/printers:go_default_library",
|
"//pkg/printers:go_default_library",
|
||||||
"//pkg/util/node: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/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"github.com/fatih/camelcase"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
@ -70,6 +71,7 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/fieldpath"
|
"k8s.io/kubernetes/pkg/fieldpath"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/qos"
|
"k8s.io/kubernetes/pkg/kubelet/qos"
|
||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
|
"k8s.io/kubernetes/pkg/util/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Each level has 2 spaces for PrefixWriter
|
// 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, "Name:\t%s\n", obj.GetName())
|
||||||
w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace())
|
w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace())
|
||||||
printLabelsMultiline(w, "Labels", obj.GetLabels())
|
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 {
|
if events != nil {
|
||||||
DescribeEvents(events, w)
|
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.
|
// DefaultObjectDescriber can describe the default Kubernetes objects.
|
||||||
var DefaultObjectDescriber printers.ObjectDescriber
|
var DefaultObjectDescriber printers.ObjectDescriber
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/kubernetes/federation/apis/federation"
|
"k8s.io/kubernetes/federation/apis/federation"
|
||||||
fedfake "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset/fake"
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,6 +49,20 @@ func ShuffleStrings(s []string) []string {
|
||||||
return shuffled
|
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,
|
// Int64Slice attaches the methods of Interface to []int64,
|
||||||
// sorting in increasing order.
|
// sorting in increasing order.
|
||||||
type Int64Slice []int64
|
type Int64Slice []int64
|
||||||
|
|
Loading…
Reference in New Issue