Add a new default printer handler for HumanReadable

Refactors and removes the need for the more complex old code, temporarily limits what output is shown for truly unknown objects (a follow up change will allow server side handling and generic fallback), and removes all of the generic printers in favor of a single code path.
parent 36621847
......@@ -25,8 +25,6 @@ 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/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
......@@ -69,11 +67,3 @@ 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"],
)
/*
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)
}
}
}
......@@ -76,7 +76,6 @@ go_library(
"//pkg/apis/networking:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/settings:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/apis/storage/util:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
......
......@@ -30,6 +30,7 @@ import (
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
apiv1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
"k8s.io/apimachinery/pkg/labels"
......@@ -48,7 +49,6 @@ import (
"k8s.io/kubernetes/pkg/apis/networking"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/settings"
"k8s.io/kubernetes/pkg/apis/storage"
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
"k8s.io/kubernetes/pkg/controller"
......@@ -69,8 +69,6 @@ var (
nodeColumns = []string{"NAME", "STATUS", "AGE", "VERSION"}
nodeWideColumns = []string{"EXTERNAL-IP", "OS-IMAGE", "KERNEL-VERSION", "CONTAINER-RUNTIME"}
eventColumns = []string{"LASTSEEN", "FIRSTSEEN", "COUNT", "NAME", "KIND", "SUBOBJECT", "TYPE", "REASON", "SOURCE", "MESSAGE"}
limitRangeColumns = []string{"NAME", "AGE"}
resourceQuotaColumns = []string{"NAME", "AGE"}
namespaceColumns = []string{"NAME", "STATUS", "AGE"}
secretColumns = []string{"NAME", "TYPE", "DATA", "AGE"}
serviceAccountColumns = []string{"NAME", "SECRETS", "AGE"}
......@@ -78,10 +76,8 @@ var (
persistentVolumeClaimColumns = []string{"NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESSMODES", "STORAGECLASS", "AGE"}
componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"}
thirdPartyResourceColumns = []string{"NAME", "DESCRIPTION", "VERSION(S)"}
roleColumns = []string{"NAME", "AGE"}
roleBindingColumns = []string{"NAME", "AGE"}
roleBindingWideColumns = []string{"ROLE", "USERS", "GROUPS", "SERVICEACCOUNTS"}
clusterRoleColumns = []string{"NAME", "AGE"}
clusterRoleBindingColumns = []string{"NAME", "AGE"}
clusterRoleBindingWideColumns = []string{"ROLE", "USERS", "GROUPS", "SERVICEACCOUNTS"}
storageClassColumns = []string{"NAME", "PROVISIONER"}
......@@ -215,10 +211,6 @@ func AddHandlers(h printers.PrintHandler) {
h.Handler(nodeColumns, nodeWideColumns, printNodeList)
h.Handler(eventColumns, nil, printEvent)
h.Handler(eventColumns, nil, printEventList)
h.Handler(limitRangeColumns, nil, printLimitRange)
h.Handler(limitRangeColumns, nil, printLimitRangeList)
h.Handler(resourceQuotaColumns, nil, printResourceQuota)
h.Handler(resourceQuotaColumns, nil, printResourceQuotaList)
h.Handler(namespaceColumns, nil, printNamespace)
h.Handler(namespaceColumns, nil, printNamespaceList)
h.Handler(secretColumns, nil, printSecret)
......@@ -249,23 +241,59 @@ func AddHandlers(h printers.PrintHandler) {
h.Handler(networkPolicyColumns, nil, printExtensionsNetworkPolicyList)
h.Handler(networkPolicyColumns, nil, printNetworkPolicy)
h.Handler(networkPolicyColumns, nil, printNetworkPolicyList)
h.Handler(roleColumns, nil, printRole)
h.Handler(roleColumns, nil, printRoleList)
h.Handler(roleBindingColumns, roleBindingWideColumns, printRoleBinding)
h.Handler(roleBindingColumns, roleBindingWideColumns, printRoleBindingList)
h.Handler(clusterRoleColumns, nil, printClusterRole)
h.Handler(clusterRoleColumns, nil, printClusterRoleList)
h.Handler(clusterRoleBindingColumns, clusterRoleBindingWideColumns, printClusterRoleBinding)
h.Handler(clusterRoleBindingColumns, clusterRoleBindingWideColumns, printClusterRoleBindingList)
h.Handler(certificateSigningRequestColumns, nil, printCertificateSigningRequest)
h.Handler(certificateSigningRequestColumns, nil, printCertificateSigningRequestList)
h.Handler(storageClassColumns, nil, printStorageClass)
h.Handler(storageClassColumns, nil, printStorageClassList)
h.Handler(podPresetColumns, nil, printPodPreset)
h.Handler(podPresetColumns, nil, printPodPresetList)
h.Handler(statusColumns, nil, printStatus)
h.Handler(controllerRevisionColumns, nil, printControllerRevision)
h.Handler(controllerRevisionColumns, nil, printControllerRevisionList)
AddDefaultHandlers(h)
}
// AddDefaultHandlers adds handlers that can work with most Kubernetes objects.
func AddDefaultHandlers(h printers.PrintHandler) {
// types without defined columns
objectMetaColumnDefinitions := []metav1alpha1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
h.DefaultTableHandler(objectMetaColumnDefinitions, printObjectMeta)
}
func printObjectMeta(obj runtime.Object, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) {
if meta.IsListType(obj) {
rows := make([]metav1alpha1.TableRow, 0, 16)
err := meta.EachListItem(obj, func(obj runtime.Object) error {
nestedRows, err := printObjectMeta(obj, options)
if err != nil {
return err
}
rows = append(rows, nestedRows...)
return nil
})
if err != nil {
return nil, err
}
return rows, nil
}
rows := make([]metav1alpha1.TableRow, 0, 1)
m, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
row := metav1alpha1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, m.GetName(), translateTimestamp(m.GetCreationTimestamp()))
rows = append(rows, row)
return rows, nil
}
// Pass ports=nil for all ports.
......@@ -1238,72 +1266,6 @@ func printEventList(list *api.EventList, w io.Writer, options printers.PrintOpti
return nil
}
func printLimitRange(limitRange *api.LimitRange, w io.Writer, options printers.PrintOptions) error {
return printObjectMeta(limitRange.ObjectMeta, w, options, true)
}
// Prints the LimitRangeList in a human-friendly format.
func printLimitRangeList(list *api.LimitRangeList, w io.Writer, options printers.PrintOptions) error {
for i := range list.Items {
if err := printLimitRange(&list.Items[i], w, options); err != nil {
return err
}
}
return nil
}
// printObjectMeta prints the object metadata of a given resource.
func printObjectMeta(meta metav1.ObjectMeta, w io.Writer, options printers.PrintOptions, namespaced bool) error {
name := printers.FormatResourceName(options.Kind, meta.Name, options.WithKind)
if namespaced && options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", meta.Namespace); err != nil {
return err
}
}
if _, err := fmt.Fprintf(
w, "%s\t%s",
name,
translateTimestamp(meta.CreationTimestamp),
); err != nil {
return err
}
if _, err := fmt.Fprint(w, printers.AppendLabels(meta.Labels, options.ColumnLabels)); err != nil {
return err
}
_, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, meta.Labels))
return err
}
func printResourceQuota(resourceQuota *api.ResourceQuota, w io.Writer, options printers.PrintOptions) error {
return printObjectMeta(resourceQuota.ObjectMeta, w, options, true)
}
// Prints the ResourceQuotaList in a human-friendly format.
func printResourceQuotaList(list *api.ResourceQuotaList, w io.Writer, options printers.PrintOptions) error {
for i := range list.Items {
if err := printResourceQuota(&list.Items[i], w, options); err != nil {
return err
}
}
return nil
}
func printRole(role *rbac.Role, w io.Writer, options printers.PrintOptions) error {
return printObjectMeta(role.ObjectMeta, w, options, true)
}
// Prints the Role in a human-friendly format.
func printRoleList(list *rbac.RoleList, w io.Writer, options printers.PrintOptions) error {
for i := range list.Items {
if err := printRole(&list.Items[i], w, options); err != nil {
return err
}
}
return nil
}
func printRoleBinding(roleBinding *rbac.RoleBinding, w io.Writer, options printers.PrintOptions) error {
meta := roleBinding.ObjectMeta
name := printers.FormatResourceName(options.Kind, meta.Name, options.WithKind)
......@@ -1352,23 +1314,6 @@ func printRoleBindingList(list *rbac.RoleBindingList, w io.Writer, options print
return nil
}
func printClusterRole(clusterRole *rbac.ClusterRole, w io.Writer, options printers.PrintOptions) error {
if options.WithNamespace {
return fmt.Errorf("clusterRole is not namespaced")
}
return printObjectMeta(clusterRole.ObjectMeta, w, options, false)
}
// Prints the ClusterRole in a human-friendly format.
func printClusterRoleList(list *rbac.ClusterRoleList, w io.Writer, options printers.PrintOptions) error {
for i := range list.Items {
if err := printClusterRole(&list.Items[i], w, options); err != nil {
return err
}
}
return nil
}
func printClusterRoleBinding(clusterRoleBinding *rbac.ClusterRoleBinding, w io.Writer, options printers.PrintOptions) error {
meta := clusterRoleBinding.ObjectMeta
name := printers.FormatResourceName(options.Kind, meta.Name, options.WithKind)
......@@ -1871,19 +1816,6 @@ func printStorageClassList(scList *storage.StorageClassList, w io.Writer, option
return nil
}
func printPodPreset(podPreset *settings.PodPreset, w io.Writer, options printers.PrintOptions) error {
return printObjectMeta(podPreset.ObjectMeta, w, options, false)
}
func printPodPresetList(list *settings.PodPresetList, w io.Writer, options printers.PrintOptions) error {
for i := range list.Items {
if err := printPodPreset(&list.Items[i], w, options); err != nil {
return err
}
}
return nil
}
func printStatus(status *metav1.Status, w io.Writer, options printers.PrintOptions) error {
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", status.Status, status.Reason, status.Message); err != nil {
return err
......
......@@ -22,6 +22,7 @@ import (
"fmt"
"io"
"reflect"
"regexp"
"strconv"
"strings"
"testing"
......@@ -103,6 +104,112 @@ func TestPrintDefault(t *testing.T) {
}
}
func TestPrintUnstructuredObject(t *testing.T) {
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",
"labels": map[string]interface{}{"test": "other"},
},
/*"items": []interface{}{
map[string]interface{}{
"itemBool": true,
"itemInt": 42,
},
},*/
"url": "http://localhost",
"status": "ok",
},
}
tests := []struct {
expected string
options printers.PrintOptions
object runtime.Object
}{
{
expected: "NAME\\s+AGE\nMyName\\s+\\d+",
object: obj,
},
{
options: printers.PrintOptions{
WithNamespace: true,
},
expected: "NAMESPACE\\s+NAME\\s+AGE\nMyNamespace\\s+MyName\\s+\\d+",
object: obj,
},
{
options: printers.PrintOptions{
ShowLabels: true,
WithNamespace: true,
},
expected: "NAMESPACE\\s+NAME\\s+AGE\\s+LABELS\nMyNamespace\\s+MyName\\s+\\d+\\w+\\s+test\\=other",
object: obj,
},
{
expected: "NAME\\s+AGE\nMyName\\s+\\d+\\w+\nMyName2\\s+\\d+",
object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Test",
"dummy1": "present",
"dummy2": "present",
"items": []interface{}{
map[string]interface{}{
"metadata": map[string]interface{}{
"name": "MyName",
"namespace": "MyNamespace",
"creationTimestamp": "2017-04-01T00:00:00Z",
"resourceVersion": 123,
"uid": "00000000-0000-0000-0000-000000000001",
"dummy3": "present",
"labels": map[string]interface{}{"test": "other"},
},
},
map[string]interface{}{
"metadata": map[string]interface{}{
"name": "MyName2",
"namespace": "MyNamespace",
"creationTimestamp": "2017-04-01T00:00:00Z",
"resourceVersion": 123,
"uid": "00000000-0000-0000-0000-000000000001",
"dummy3": "present",
"labels": "badlabel",
},
},
},
"url": "http://localhost",
"status": "ok",
},
},
},
}
out := bytes.NewBuffer([]byte{})
for _, test := range tests {
out.Reset()
printer := printers.NewHumanReadablePrinter(nil, nil, test.options).With(AddDefaultHandlers)
printer.PrintObj(test.object, out)
matches, err := regexp.MatchString(test.expected, out.String())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !matches {
t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out)
}
}
}
type TestPrintType struct {
Data string
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment