diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go index bfaaf9270e1..6c1e725b1b5 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go @@ -24,6 +24,7 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/discovery" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" @@ -44,7 +45,8 @@ import ( type TopPodOptions struct { ResourceName string Namespace string - Selector string + LabelSelector string + FieldSelector string SortBy string AllNamespaces bool PrintContainers bool @@ -106,7 +108,8 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions }, Aliases: []string{"pods", "po"}, } - 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().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort pods list using specified field. The field can be either 'cpu' or 'memory'.") cmd.Flags().BoolVar(&o.PrintContainers, "containers", o.PrintContainers, "If present, print usage of containers within a pod.") cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") @@ -157,17 +160,24 @@ func (o *TopPodOptions) Validate() error { return errors.New("--sort-by accepts only cpu or memory") } } - if len(o.ResourceName) > 0 && len(o.Selector) > 0 { - return errors.New("only one of NAME or --selector can be provided") + if len(o.ResourceName) > 0 && (len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0) { + return errors.New("only one of NAME or selector can be provided") } return nil } func (o TopPodOptions) RunTopPod() error { var err error - selector := labels.Everything() - if len(o.Selector) > 0 { - selector, err = labels.Parse(o.Selector) + labelSelector := labels.Everything() + if len(o.LabelSelector) > 0 { + labelSelector, err = labels.Parse(o.LabelSelector) + if err != nil { + return err + } + } + fieldSelector := fields.Everything() + if len(o.FieldSelector) > 0 { + fieldSelector, err = fields.ParseSelector(o.FieldSelector) if err != nil { return err } @@ -183,7 +193,7 @@ func (o TopPodOptions) RunTopPod() error { if !metricsAPIAvailable { return errors.New("Metrics API not available") } - metrics, err := getMetricsFromMetricsAPI(o.MetricsClient, o.Namespace, o.ResourceName, o.AllNamespaces, selector) + metrics, err := getMetricsFromMetricsAPI(o.MetricsClient, o.Namespace, o.ResourceName, o.AllNamespaces, labelSelector, fieldSelector) if err != nil { return err } @@ -192,7 +202,7 @@ func (o TopPodOptions) RunTopPod() error { if len(metrics.Items) == 0 { // If the API server query is successful but all the pods are newly created, // the metrics are probably not ready yet, so we return the error here in the first place. - err := verifyEmptyMetrics(o, selector) + err := verifyEmptyMetrics(o, labelSelector, fieldSelector) if err != nil { return err } @@ -208,7 +218,7 @@ func (o TopPodOptions) RunTopPod() error { return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces, o.NoHeaders, o.SortBy) } -func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, selector labels.Selector) (*metricsapi.PodMetricsList, error) { +func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, labelSelector labels.Selector, fieldSelector fields.Selector) (*metricsapi.PodMetricsList, error) { var err error ns := metav1.NamespaceAll if !allNamespaces { @@ -222,7 +232,7 @@ func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespac } versionedMetrics.Items = []metricsv1beta1api.PodMetrics{*m} } else { - versionedMetrics, err = metricsClient.MetricsV1beta1().PodMetricses(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()}) + versionedMetrics, err = metricsClient.MetricsV1beta1().PodMetricses(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String(), FieldSelector: fieldSelector.String()}) if err != nil { return nil, err } @@ -235,7 +245,7 @@ func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespac return metrics, nil } -func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error { +func verifyEmptyMetrics(o TopPodOptions, labelSelector labels.Selector, fieldSelector fields.Selector) error { if len(o.ResourceName) > 0 { pod, err := o.PodClient.Pods(o.Namespace).Get(context.TODO(), o.ResourceName, metav1.GetOptions{}) if err != nil { @@ -246,7 +256,8 @@ func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error { } } else { pods, err := o.PodClient.Pods(o.Namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: selector.String(), + LabelSelector: labelSelector.String(), + FieldSelector: fieldSelector.String(), }) if err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go index d8b575cc79f..e6a0096deb7 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go @@ -107,10 +107,16 @@ func TestTopPod(t *testing.T) { }, { name: "pod with label selector", - options: &TopPodOptions{Selector: "key=value"}, + options: &TopPodOptions{LabelSelector: "key=value"}, expectedQuery: "labelSelector=" + url.QueryEscape("key=value"), namespaces: []string{testNS, testNS}, }, + { + name: "pod with field selector", + options: &TopPodOptions{FieldSelector: "key=value"}, + expectedQuery: "fieldSelector=" + url.QueryEscape("key=value"), + namespaces: []string{testNS, testNS}, + }, { name: "pod with container metrics", options: &TopPodOptions{PrintContainers: true},