From c9b6c92f10191d6c13f119569b7b95d98fc7875c Mon Sep 17 00:00:00 2001 From: WanLinghao Date: Mon, 27 Aug 2018 19:27:48 +0800 Subject: [PATCH] add flag `--no-headers` to `kubectl top ...` --- pkg/kubectl/cmd/top_node.go | 5 ++++- pkg/kubectl/cmd/top_node_test.go | 4 ++++ pkg/kubectl/cmd/top_pod.go | 4 +++- pkg/kubectl/cmd/top_pod_test.go | 12 +++++++++++ pkg/kubectl/metricsutil/metrics_printer.go | 25 +++++++++++----------- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/pkg/kubectl/cmd/top_node.go b/pkg/kubectl/cmd/top_node.go index c224d3f36e..2c4b77657a 100644 --- a/pkg/kubectl/cmd/top_node.go +++ b/pkg/kubectl/cmd/top_node.go @@ -40,6 +40,7 @@ import ( type TopNodeOptions struct { ResourceName string Selector string + NoHeaders bool NodeClient corev1client.CoreV1Interface HeapsterOptions HeapsterTopOptions Client *metricsutil.HeapsterMetricsClient @@ -118,6 +119,8 @@ func NewCmdTopNode(f cmdutil.Factory, o *TopNodeOptions, streams genericclioptio Aliases: []string{"nodes", "no"}, } cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers") + o.HeapsterOptions.Bind(cmd.Flags()) return cmd } @@ -216,7 +219,7 @@ func (o TopNodeOptions) RunTopNode() error { allocatable[n.Name] = n.Status.Allocatable } - return o.Printer.PrintNodeMetrics(metrics.Items, allocatable) + return o.Printer.PrintNodeMetrics(metrics.Items, allocatable, o.NoHeaders) } func getNodeMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, resourceName string, selector labels.Selector) (*metricsapi.NodeMetricsList, error) { diff --git a/pkg/kubectl/cmd/top_node_test.go b/pkg/kubectl/cmd/top_node_test.go index 8abf81ae1f..ec81d7fd8e 100644 --- a/pkg/kubectl/cmd/top_node_test.go +++ b/pkg/kubectl/cmd/top_node_test.go @@ -81,6 +81,7 @@ func TestTopNodeAllMetrics(t *testing.T) { streams, _, buf, _ := genericclioptions.NewTestIOStreams() cmd := NewCmdTopNode(tf, nil, streams) + cmd.Flags().Set("no-headers", "true") cmd.Run(cmd, []string{}) // Check the presence of node names in the output. @@ -90,6 +91,9 @@ func TestTopNodeAllMetrics(t *testing.T) { t.Errorf("missing metrics for %s: \n%s", m.Name, result) } } + if strings.Contains(result, "MEMORY") { + t.Errorf("should not print headers with --no-headers option set:\n%s\n", result) + } } func TestTopNodeAllMetricsCustomDefaults(t *testing.T) { diff --git a/pkg/kubectl/cmd/top_pod.go b/pkg/kubectl/cmd/top_pod.go index e3a2c35e51..657d1ff5a0 100644 --- a/pkg/kubectl/cmd/top_pod.go +++ b/pkg/kubectl/cmd/top_pod.go @@ -45,6 +45,7 @@ type TopPodOptions struct { Selector string AllNamespaces bool PrintContainers bool + NoHeaders bool PodClient corev1client.PodsGetter HeapsterOptions HeapsterTopOptions Client *metricsutil.HeapsterMetricsClient @@ -109,6 +110,7 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") cmd.Flags().BoolVar(&o.PrintContainers, "containers", o.PrintContainers, "If present, print usage of containers within a pod.") cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") + cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers.") o.HeapsterOptions.Bind(cmd.Flags()) return cmd } @@ -198,7 +200,7 @@ func (o TopPodOptions) RunTopPod() error { return err } - return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces) + return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces, o.NoHeaders) } func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, selector labels.Selector) (*metricsapi.PodMetricsList, error) { diff --git a/pkg/kubectl/cmd/top_pod_test.go b/pkg/kubectl/cmd/top_pod_test.go index 6ee773c6ed..96148c49e0 100644 --- a/pkg/kubectl/cmd/top_pod_test.go +++ b/pkg/kubectl/cmd/top_pod_test.go @@ -37,6 +37,7 @@ import ( "k8s.io/client-go/rest/fake" core "k8s.io/client-go/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/scheme" metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1" metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" @@ -132,6 +133,14 @@ func TestTopPod(t *testing.T) { namespaces: []string{testNS}, containers: true, }, + { + name: "no-headers set", + flags: map[string]string{"containers": "true", "no-headers": "true"}, + args: []string{"pod1"}, + expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods/pod1", + namespaces: []string{testNS}, + containers: true, + }, } initTestErrorHandler(t) for _, testCase := range testCases { @@ -221,6 +230,9 @@ func TestTopPod(t *testing.T) { t.Errorf("%s: unexpected metrics for %s: \n%s", testCase.name, name, result) } } + if cmdutil.GetFlagBool(cmd, "no-headers") && strings.Contains(result, "MEMORY") { + t.Errorf("%s: unexpected headers with no-headers option set: \n%s", testCase.name, result) + } }) } } diff --git a/pkg/kubectl/metricsutil/metrics_printer.go b/pkg/kubectl/metricsutil/metrics_printer.go index 9e1f45b25c..b13c39273d 100644 --- a/pkg/kubectl/metricsutil/metrics_printer.go +++ b/pkg/kubectl/metricsutil/metrics_printer.go @@ -53,7 +53,7 @@ func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter { return &TopCmdPrinter{out: out} } -func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList) error { +func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool) error { if len(metrics) == 0 { return nil } @@ -63,8 +63,9 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, sort.Slice(metrics, func(i, j int) bool { return metrics[i].Name < metrics[j].Name }) - - printColumnNames(w, NodeColumns) + if !noHeaders { + printColumnNames(w, NodeColumns) + } var usage v1.ResourceList for _, m := range metrics { err := scheme.Scheme.Convert(&m.Usage, &usage, nil) @@ -86,18 +87,20 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, return nil } -func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool) error { +func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, noHeaders bool) error { if len(metrics) == 0 { return nil } w := printers.GetNewTabWriter(printer.out) defer w.Flush() - - if withNamespace { - printValue(w, NamespaceColumn) - } - if printContainers { - printValue(w, PodColumn) + if !noHeaders { + if withNamespace { + printValue(w, NamespaceColumn) + } + if printContainers { + printValue(w, PodColumn) + } + printColumnNames(w, PodColumns) } sort.Slice(metrics, func(i, j int) bool { @@ -106,8 +109,6 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, p } return metrics[i].Name < metrics[j].Name }) - - printColumnNames(w, PodColumns) for _, m := range metrics { err := printSinglePodMetrics(w, &m, printContainers, withNamespace) if err != nil {