Print resource labels as columns

This commit is contained in:
Anastasis Andronidis 2015-06-16 18:30:11 +02:00
parent ff0546da4f
commit b06ef70057
11 changed files with 268 additions and 103 deletions

View File

@ -232,6 +232,8 @@ _kubectl_get()
flags+=("--all-namespaces")
flags+=("--help")
flags+=("-h")
flags+=("--label-columns=")
two_word_flags+=("-L")
flags+=("--no-headers")
flags+=("--output=")
two_word_flags+=("-o")

View File

@ -46,6 +46,7 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7
```
--all-namespaces=false: If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.
-h, --help=false: help for get
-L, --label-columns=[]: Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag statements like -L label1 -L label2...
--no-headers=false: When using the default output, don't print headers.
-o, --output="": Output format. One of: json|yaml|template|templatefile.
--output-version="": Output the formatted object with the given version (default api-version).
@ -87,6 +88,6 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7
### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-06-05 21:08:36.511279339 +0000 UTC
###### Auto generated by spf13/cobra at 2015-06-21 22:41:03.746552518 +0000 UTC
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_get.md?pixel)]()

View File

@ -35,6 +35,10 @@ of the \-\-template flag, you can filter the attributes of the fetched resource(
\fB\-h\fP, \fB\-\-help\fP=false
help for get
.PP
\fB\-L\fP, \fB\-\-label\-columns\fP=[]
Accepts a comma separated list of labels that are going to be presented as columns. Names are case\-sensitive. You can also use multiple flag statements like \-L label1 \-L label2...
.PP
\fB\-\-no\-headers\fP=false
When using the default output, don't print headers.

View File

@ -15,6 +15,7 @@ limitations under the License.
*/
// A set of common functions needed by cmd/kubectl and pkg/kubectl packages.
package kubectl
import (
@ -38,3 +39,15 @@ func AddJsonFilenameFlag(cmd *cobra.Command, value *util.StringList, usage strin
}
cmd.Flags().AddFlag(flag)
}
// AddLabelsToColumnsFlag added a user flag to print resource labels into columns. Currently used in kubectl get command
func AddLabelsToColumnsFlag(cmd *cobra.Command, value *util.StringList, usage string) {
flag := &pflag.Flag{
Name: "label-columns",
Shorthand: "L",
Usage: usage,
Value: value,
DefValue: value.String(),
}
cmd.Flags().AddFlag(flag)
}

View File

@ -140,7 +140,7 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Describer: func(*meta.RESTMapping) (kubectl.Describer, error) {
return t.Describer, t.Err
},
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool) (kubectl.ResourcePrinter, error) {
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, columnLabels []string) (kubectl.ResourcePrinter, error) {
return t.Printer, t.Err
},
Validator: func() (validation.Schema, error) {
@ -194,7 +194,7 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Describer: func(*meta.RESTMapping) (kubectl.Describer, error) {
return t.Describer, t.Err
},
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool) (kubectl.ResourcePrinter, error) {
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, columnLabels []string) (kubectl.ResourcePrinter, error) {
return t.Printer, t.Err
},
Validator: func() (validation.Schema, error) {
@ -243,7 +243,7 @@ func stringBody(body string) io.ReadCloser {
func ExamplePrintReplicationController() {
f, tf, codec := NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(false, false)
tf.Printer = kubectl.NewHumanReadablePrinter(false, false, []string{})
tf.Client = &client.FakeRESTClient{
Codec: codec,
Client: nil,

View File

@ -24,6 +24,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/spf13/cobra"
@ -61,7 +62,7 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7`
// NewCmdGet creates a command object for the generic "get" action, which
// retrieves one or more resources from a server.
func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command {
p := kubectl.NewHumanReadablePrinter(false, false)
p := kubectl.NewHumanReadablePrinter(false, false, []string{})
validArgs := p.HandledResources()
cmd := &cobra.Command{
@ -80,6 +81,7 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().BoolP("watch", "w", false, "After listing/getting the requested object, watch for changes.")
cmd.Flags().Bool("watch-only", false, "Watch for changes to the requested object(s), without listing/getting first.")
cmd.Flags().Bool("all-namespaces", false, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
kubectl.AddLabelsToColumnsFlag(cmd, &util.StringList{}, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag statements like -L label1 -L label2...")
return cmd
}

View File

@ -64,7 +64,7 @@ type Factory struct {
// Returns a Describer for displaying the specified RESTMapping type or an error.
Describer func(mapping *meta.RESTMapping) (kubectl.Describer, error)
// Returns a Printer for formatting objects of the given type or an error.
Printer func(mapping *meta.RESTMapping, noHeaders, withNamespace bool) (kubectl.ResourcePrinter, error)
Printer func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, columnLabels []string) (kubectl.ResourcePrinter, error)
// Returns a Scaler for changing the size of the specified RESTMapping type or an error
Scaler func(mapping *meta.RESTMapping) (kubectl.Scaler, error)
// Returns a Reaper for gracefully shutting down resources.
@ -140,8 +140,8 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
}
return describer, nil
},
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool) (kubectl.ResourcePrinter, error) {
return kubectl.NewHumanReadablePrinter(noHeaders, withNamespace), nil
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, columnLabels []string) (kubectl.ResourcePrinter, error) {
return kubectl.NewHumanReadablePrinter(noHeaders, withNamespace, columnLabels), nil
},
PodSelectorForObject: func(object runtime.Object) (string, error) {
// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
@ -378,7 +378,7 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin
}
printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version, mapping.APIVersion)
} else {
printer, err = f.Printer(mapping, GetFlagBool(cmd, "no-headers"), withNamespace)
printer, err = f.Printer(mapping, GetFlagBool(cmd, "no-headers"), withNamespace, GetFlagStringList(cmd, "label-columns"))
if err != nil {
return nil, err
}

View File

@ -35,6 +35,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
utilerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
"github.com/evanphx/json-patch"
@ -250,6 +251,15 @@ func GetFlagString(cmd *cobra.Command, flag string) string {
return f.Value.String()
}
// GetFlagStringList can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...)
func GetFlagStringList(cmd *cobra.Command, flag string) util.StringList {
f := cmd.Flags().Lookup(flag)
if f == nil {
return util.StringList{}
}
return *f.Value.(*util.StringList)
}
func GetFlagBool(cmd *cobra.Command, flag string) bool {
f := getFlag(cmd, flag)
result, err := strconv.ParseBool(f.Value.String())

View File

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
)
// AddPrinterFlags adds printing related flags to a command (e.g. output format, no headers, template path)
func AddPrinterFlags(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|template|templatefile.")
cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version).")

View File

@ -180,15 +180,17 @@ type HumanReadablePrinter struct {
handlerMap map[reflect.Type]*handlerEntry
noHeaders bool
withNamespace bool
columnLabels []string
lastType reflect.Type
}
// NewHumanReadablePrinter creates a HumanReadablePrinter.
func NewHumanReadablePrinter(noHeaders, withNamespace bool) *HumanReadablePrinter {
func NewHumanReadablePrinter(noHeaders, withNamespace bool, columnLabels []string) *HumanReadablePrinter {
printer := &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
noHeaders: noHeaders,
withNamespace: withNamespace,
columnLabels: columnLabels,
}
printer.addDefaultHandlers()
return printer
@ -215,17 +217,18 @@ func (h *HumanReadablePrinter) Handler(columns []string, printFunc interface{})
func (h *HumanReadablePrinter) validatePrintHandlerFunc(printFunc reflect.Value) error {
if printFunc.Kind() != reflect.Func {
return fmt.Errorf("invalid print handler. %#v is not a function.", printFunc)
return fmt.Errorf("invalid print handler. %#v is not a function", printFunc)
}
funcType := printFunc.Type()
if funcType.NumIn() != 3 || funcType.NumOut() != 1 {
if funcType.NumIn() != 4 || funcType.NumOut() != 1 {
return fmt.Errorf("invalid print handler." +
"Must accept 3 parameters and return 1 value.")
}
if funcType.In(1) != reflect.TypeOf((*io.Writer)(nil)).Elem() ||
funcType.In(3) != reflect.TypeOf((*[]string)(nil)).Elem() ||
funcType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("invalid print handler. The expected signature is: "+
"func handler(obj %v, w io.Writer, withNamespace bool) error", funcType.In(0))
"func handler(obj %v, w io.Writer, withNamespace bool, columnLabels []string) error", funcType.In(0))
}
return nil
}
@ -368,7 +371,7 @@ func translateTimestamp(timestamp util.Time) string {
return shortHumanDuration(time.Now().Sub(timestamp.Time))
}
func printPod(pod *api.Pod, w io.Writer, withNamespace bool) error {
func printPod(pod *api.Pod, w io.Writer, withNamespace bool, columnLabels []string) error {
name := pod.Name
if withNamespace {
name = types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}.String()
@ -398,29 +401,30 @@ func printPod(pod *api.Pod, w io.Writer, withNamespace bool) error {
}
}
_, err := fmt.Fprintf(w, "%s\t%d/%d\t%s\t%d\t%s\n",
if _, err := fmt.Fprintf(w, "%s\t%d/%d\t%s\t%d\t%s",
name,
readyContainers,
totalContainers,
reason,
restarts,
translateTimestamp(pod.CreationTimestamp))
if err != nil {
translateTimestamp(pod.CreationTimestamp),
); err != nil {
return err
}
return nil
_, err := fmt.Fprint(w, appendLabels(pod.Labels, columnLabels))
return err
}
func printPodList(podList *api.PodList, w io.Writer, withNamespace bool) error {
func printPodList(podList *api.PodList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, pod := range podList.Items {
if err := printPod(&pod, w, withNamespace); err != nil {
if err := printPod(&pod, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printPodTemplate(pod *api.PodTemplate, w io.Writer, withNamespace bool) error {
func printPodTemplate(pod *api.PodTemplate, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{pod.Namespace, pod.Name}.String()
@ -433,15 +437,19 @@ func printPodTemplate(pod *api.PodTemplate, w io.Writer, withNamespace bool) err
if len(containers) > 0 {
firstContainer, containers = containers[0], containers[1:]
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s",
name,
firstContainer.Name,
firstContainer.Image,
formatLabels(pod.Template.Labels),
)
if err != nil {
); err != nil {
return err
}
if _, err := fmt.Fprint(w, appendLabels(pod.Labels, columnLabels)); err != nil {
return err
}
// Lay out all the other containers on separate lines.
for _, container := range containers {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "", container.Name, container.Image, "")
@ -452,16 +460,16 @@ func printPodTemplate(pod *api.PodTemplate, w io.Writer, withNamespace bool) err
return nil
}
func printPodTemplateList(podList *api.PodTemplateList, w io.Writer, withNamespace bool) error {
func printPodTemplateList(podList *api.PodTemplateList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, pod := range podList.Items {
if err := printPodTemplate(&pod, w, withNamespace); err != nil {
if err := printPodTemplate(&pod, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printReplicationController(controller *api.ReplicationController, w io.Writer, withNamespace bool) error {
func printReplicationController(controller *api.ReplicationController, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{controller.Namespace, controller.Name}.String()
@ -474,15 +482,20 @@ func printReplicationController(controller *api.ReplicationController, w io.Writ
if len(containers) > 0 {
firstContainer, containers = containers[0], containers[1:]
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n",
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d",
name,
firstContainer.Name,
firstContainer.Image,
formatLabels(controller.Spec.Selector),
controller.Spec.Replicas)
if err != nil {
controller.Spec.Replicas,
); err != nil {
return err
}
if _, err := fmt.Fprint(w, appendLabels(controller.Labels, columnLabels)); err != nil {
return err
}
// Lay out all the other containers on separate lines.
for _, container := range containers {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", "", container.Name, container.Image, "", "")
@ -493,16 +506,16 @@ func printReplicationController(controller *api.ReplicationController, w io.Writ
return nil
}
func printReplicationControllerList(list *api.ReplicationControllerList, w io.Writer, withNamespace bool) error {
func printReplicationControllerList(list *api.ReplicationControllerList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, controller := range list.Items {
if err := printReplicationController(&controller, w, withNamespace); err != nil {
if err := printReplicationController(&controller, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printService(svc *api.Service, w io.Writer, withNamespace bool) error {
func printService(svc *api.Service, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{svc.Namespace, svc.Name}.String()
@ -519,10 +532,13 @@ func printService(svc *api.Service, w io.Writer, withNamespace bool) error {
}
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d/%s\n", name, formatLabels(svc.Labels),
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d/%s", name, formatLabels(svc.Labels),
formatLabels(svc.Spec.Selector), ips[0], svc.Spec.Ports[0].Port, svc.Spec.Ports[0].Protocol); err != nil {
return err
}
if _, err := fmt.Fprint(w, appendLabels(svc.Labels, columnLabels)); err != nil {
return err
}
count := len(svc.Spec.Ports)
if len(ips) > count {
@ -546,50 +562,57 @@ func printService(svc *api.Service, w io.Writer, withNamespace bool) error {
return nil
}
func printServiceList(list *api.ServiceList, w io.Writer, withNamespace bool) error {
func printServiceList(list *api.ServiceList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, svc := range list.Items {
if err := printService(&svc, w, withNamespace); err != nil {
if err := printService(&svc, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printEndpoints(endpoints *api.Endpoints, w io.Writer, withNamespace bool) error {
func printEndpoints(endpoints *api.Endpoints, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{endpoints.Namespace, endpoints.Name}.String()
} else {
name = endpoints.Name
}
_, err := fmt.Fprintf(w, "%s\t%s\n", name, formatEndpoints(endpoints, nil))
if _, err := fmt.Fprintf(w, "%s\t%s", name, formatEndpoints(endpoints, nil)); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(endpoints.Labels, columnLabels))
return err
}
func printEndpointsList(list *api.EndpointsList, w io.Writer, withNamespace bool) error {
func printEndpointsList(list *api.EndpointsList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, item := range list.Items {
if err := printEndpoints(&item, w, withNamespace); err != nil {
if err := printEndpoints(&item, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printNamespace(item *api.Namespace, w io.Writer, withNamespace bool) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\n", item.Name, formatLabels(item.Labels), item.Status.Phase)
func printNamespace(item *api.Namespace, w io.Writer, withNamespace bool, columnLabels []string) error {
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", item.Name, formatLabels(item.Labels), item.Status.Phase); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(item.Labels, columnLabels))
return err
}
func printNamespaceList(list *api.NamespaceList, w io.Writer, withNamespace bool) error {
func printNamespaceList(list *api.NamespaceList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, item := range list.Items {
if err := printNamespace(&item, w, withNamespace); err != nil {
if err := printNamespace(&item, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printSecret(item *api.Secret, w io.Writer, withNamespace bool) error {
func printSecret(item *api.Secret, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{item.Namespace, item.Name}.String()
@ -597,13 +620,16 @@ func printSecret(item *api.Secret, w io.Writer, withNamespace bool) error {
name = item.Name
}
_, err := fmt.Fprintf(w, "%s\t%s\t%v\n", name, item.Type, len(item.Data))
if _, err := fmt.Fprintf(w, "%s\t%s\t%v", name, item.Type, len(item.Data)); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(item.Labels, columnLabels))
return err
}
func printSecretList(list *api.SecretList, w io.Writer, withNamespace bool) error {
func printSecretList(list *api.SecretList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, item := range list.Items {
if err := printSecret(&item, w, withNamespace); err != nil {
if err := printSecret(&item, w, withNamespace, columnLabels); err != nil {
return err
}
}
@ -611,7 +637,7 @@ func printSecretList(list *api.SecretList, w io.Writer, withNamespace bool) erro
return nil
}
func printServiceAccount(item *api.ServiceAccount, w io.Writer, withNamespace bool) error {
func printServiceAccount(item *api.ServiceAccount, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{item.Namespace, item.Name}.String()
@ -619,13 +645,16 @@ func printServiceAccount(item *api.ServiceAccount, w io.Writer, withNamespace bo
name = item.Name
}
_, err := fmt.Fprintf(w, "%s\t%d\n", name, len(item.Secrets))
if _, err := fmt.Fprintf(w, "%s\t%d", name, len(item.Secrets)); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(item.Labels, columnLabels))
return err
}
func printServiceAccountList(list *api.ServiceAccountList, w io.Writer, withNamespace bool) error {
func printServiceAccountList(list *api.ServiceAccountList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, item := range list.Items {
if err := printServiceAccount(&item, w, withNamespace); err != nil {
if err := printServiceAccount(&item, w, withNamespace, columnLabels); err != nil {
return err
}
}
@ -633,7 +662,7 @@ func printServiceAccountList(list *api.ServiceAccountList, w io.Writer, withName
return nil
}
func printNode(node *api.Node, w io.Writer, withNamespace bool) error {
func printNode(node *api.Node, w io.Writer, withNamespace bool, columnLabels []string) error {
conditionMap := make(map[api.NodeConditionType]*api.NodeCondition)
NodeAllConditions := []api.NodeConditionType{api.NodeReady}
for i := range node.Status.Conditions {
@ -656,20 +685,24 @@ func printNode(node *api.Node, w io.Writer, withNamespace bool) error {
if node.Spec.Unschedulable {
status = append(status, "SchedulingDisabled")
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\n", node.Name, formatLabels(node.Labels), strings.Join(status, ","))
if _, err := fmt.Fprintf(w, "%s\t%s\t%s", node.Name, formatLabels(node.Labels), strings.Join(status, ",")); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(node.Labels, columnLabels))
return err
}
func printNodeList(list *api.NodeList, w io.Writer, withNamespace bool) error {
func printNodeList(list *api.NodeList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, node := range list.Items {
if err := printNode(&node, w, withNamespace); err != nil {
if err := printNode(&node, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printPersistentVolume(pv *api.PersistentVolume, w io.Writer, withNamespace bool) error {
func printPersistentVolume(pv *api.PersistentVolume, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{pv.Namespace, pv.Name}.String()
@ -689,20 +722,23 @@ func printPersistentVolume(pv *api.PersistentVolume, w io.Writer, withNamespace
aQty := pv.Spec.Capacity[api.ResourceStorage]
aSize := aQty.Value()
_, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\t%s\t%s\n", name, formatLabels(pv.Labels), aSize, modesStr, pv.Status.Phase, claimRefUID, pv.Status.Reason)
if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\t%s\t%s", name, formatLabels(pv.Labels), aSize, modesStr, pv.Status.Phase, claimRefUID, pv.Status.Reason); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(pv.Labels, columnLabels))
return err
}
func printPersistentVolumeList(list *api.PersistentVolumeList, w io.Writer, withNamespace bool) error {
func printPersistentVolumeList(list *api.PersistentVolumeList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, pv := range list.Items {
if err := printPersistentVolume(&pv, w, withNamespace); err != nil {
if err := printPersistentVolume(&pv, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printPersistentVolumeClaim(pvc *api.PersistentVolumeClaim, w io.Writer, withNamespace bool) error {
func printPersistentVolumeClaim(pvc *api.PersistentVolumeClaim, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{pvc.Namespace, pvc.Name}.String()
@ -710,22 +746,25 @@ func printPersistentVolumeClaim(pvc *api.PersistentVolumeClaim, w io.Writer, wit
name = pvc.Name
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, pvc.Labels, pvc.Status.Phase, pvc.Spec.VolumeName)
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s", name, pvc.Labels, pvc.Status.Phase, pvc.Spec.VolumeName); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(pvc.Labels, columnLabels))
return err
}
func printPersistentVolumeClaimList(list *api.PersistentVolumeClaimList, w io.Writer, withNamespace bool) error {
func printPersistentVolumeClaimList(list *api.PersistentVolumeClaimList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, psd := range list.Items {
if err := printPersistentVolumeClaim(&psd, w, withNamespace); err != nil {
if err := printPersistentVolumeClaim(&psd, w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printEvent(event *api.Event, w io.Writer, withNamespace bool) error {
_, err := fmt.Fprintf(
w, "%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n",
func printEvent(event *api.Event, w io.Writer, withNamespace bool, columnLabels []string) error {
if _, err := fmt.Fprintf(
w, "%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s",
event.FirstTimestamp.Time.Format(time.RFC1123Z),
event.LastTimestamp.Time.Format(time.RFC1123Z),
event.Count,
@ -735,22 +774,25 @@ func printEvent(event *api.Event, w io.Writer, withNamespace bool) error {
event.Reason,
event.Source,
event.Message,
)
); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(event.Labels, columnLabels))
return err
}
// Sorts and prints the EventList in a human-friendly format.
func printEventList(list *api.EventList, w io.Writer, withNamespace bool) error {
func printEventList(list *api.EventList, w io.Writer, withNamespace bool, columnLabels []string) error {
sort.Sort(SortableEvents(list.Items))
for i := range list.Items {
if err := printEvent(&list.Items[i], w, withNamespace); err != nil {
if err := printEvent(&list.Items[i], w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printLimitRange(limitRange *api.LimitRange, w io.Writer, withNamespace bool) error {
func printLimitRange(limitRange *api.LimitRange, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{limitRange.Namespace, limitRange.Name}.String()
@ -758,24 +800,24 @@ func printLimitRange(limitRange *api.LimitRange, w io.Writer, withNamespace bool
name = limitRange.Name
}
_, err := fmt.Fprintf(
w, "%s\n",
name,
)
if _, err := fmt.Fprintf(w, "%s", name); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(limitRange.Labels, columnLabels))
return err
}
// Prints the LimitRangeList in a human-friendly format.
func printLimitRangeList(list *api.LimitRangeList, w io.Writer, withNamespace bool) error {
func printLimitRangeList(list *api.LimitRangeList, w io.Writer, withNamespace bool, columnLabels []string) error {
for i := range list.Items {
if err := printLimitRange(&list.Items[i], w, withNamespace); err != nil {
if err := printLimitRange(&list.Items[i], w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printResourceQuota(resourceQuota *api.ResourceQuota, w io.Writer, withNamespace bool) error {
func printResourceQuota(resourceQuota *api.ResourceQuota, w io.Writer, withNamespace bool, columnLabels []string) error {
var name string
if withNamespace {
name = types.NamespacedName{resourceQuota.Namespace, resourceQuota.Name}.String()
@ -783,24 +825,24 @@ func printResourceQuota(resourceQuota *api.ResourceQuota, w io.Writer, withNames
name = resourceQuota.Name
}
_, err := fmt.Fprintf(
w, "%s\n",
name,
)
if _, err := fmt.Fprintf(w, "%s", name); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(resourceQuota.Labels, columnLabels))
return err
}
// Prints the ResourceQuotaList in a human-friendly format.
func printResourceQuotaList(list *api.ResourceQuotaList, w io.Writer, withNamespace bool) error {
func printResourceQuotaList(list *api.ResourceQuotaList, w io.Writer, withNamespace bool, columnLabels []string) error {
for i := range list.Items {
if err := printResourceQuota(&list.Items[i], w, withNamespace); err != nil {
if err := printResourceQuota(&list.Items[i], w, withNamespace, columnLabels); err != nil {
return err
}
}
return nil
}
func printComponentStatus(item *api.ComponentStatus, w io.Writer, withNamespace bool) error {
func printComponentStatus(item *api.ComponentStatus, w io.Writer, withNamespace bool, columnLabels []string) error {
status := "Unknown"
message := ""
error := ""
@ -816,13 +858,17 @@ func printComponentStatus(item *api.ComponentStatus, w io.Writer, withNamespace
break
}
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", item.Name, status, message, error)
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s", item.Name, status, message, error); err != nil {
return err
}
_, err := fmt.Fprint(w, appendLabels(item.Labels, columnLabels))
return err
}
func printComponentStatusList(list *api.ComponentStatusList, w io.Writer, withNamespace bool) error {
func printComponentStatusList(list *api.ComponentStatusList, w io.Writer, withNamespace bool, columnLabels []string) error {
for _, item := range list.Items {
if err := printComponentStatus(&item, w, withNamespace); err != nil {
if err := printComponentStatus(&item, w, withNamespace, columnLabels); err != nil {
return err
}
}
@ -830,6 +876,31 @@ func printComponentStatusList(list *api.ComponentStatusList, w io.Writer, withNa
return nil
}
func appendLabels(itemLabels map[string]string, columnLabels []string) string {
var buffer bytes.Buffer
for _, cl := range columnLabels {
buffer.WriteString(fmt.Sprint("\t"))
if il, ok := itemLabels[cl]; ok {
buffer.WriteString(fmt.Sprint(il))
} else {
buffer.WriteString("<n/a>")
}
}
buffer.WriteString("\n")
return buffer.String()
}
func formatLabelHeaders(columnLabels []string) []string {
formHead := make([]string, len(columnLabels))
for i, l := range columnLabels {
p := strings.Split(l, "/")
formHead[i] = strings.ToUpper((p[len(p)-1]))
}
return formHead
}
// PrintObj prints the obj in a human-friendly format according to the type of the obj.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
w := tabwriter.NewWriter(output, 10, 4, 3, ' ', 0)
@ -837,10 +908,11 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil {
if !h.noHeaders && t != h.lastType {
h.printHeader(handler.columns, w)
headers := append(handler.columns, formatLabelHeaders(h.columnLabels)...)
h.printHeader(headers, w)
h.lastType = t
}
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(w), reflect.ValueOf(h.withNamespace)}
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(w), reflect.ValueOf(h.withNamespace), reflect.ValueOf(h.columnLabels)}
resultValue := handler.printFunc.Call(args)[0]
if resultValue.IsNil() {
return nil

View File

@ -237,18 +237,18 @@ type TestUnknownType struct{}
func (*TestUnknownType) IsAnAPIObject() {}
func PrintCustomType(obj *TestPrintType, w io.Writer, withNamespace bool) error {
func PrintCustomType(obj *TestPrintType, w io.Writer, withNamespace bool, columnLabels []string) error {
_, err := fmt.Fprintf(w, "%s", obj.Data)
return err
}
func ErrorPrintHandler(obj *TestPrintType, w io.Writer, withNamespace bool) error {
func ErrorPrintHandler(obj *TestPrintType, w io.Writer, withNamespace bool, columnLabels []string) error {
return fmt.Errorf("ErrorPrintHandler error")
}
func TestCustomTypePrinting(t *testing.T) {
columns := []string{"Data"}
printer := NewHumanReadablePrinter(false, false)
printer := NewHumanReadablePrinter(false, false, []string{})
printer.Handler(columns, PrintCustomType)
obj := TestPrintType{"test object"}
@ -265,7 +265,7 @@ func TestCustomTypePrinting(t *testing.T) {
func TestPrintHandlerError(t *testing.T) {
columns := []string{"Data"}
printer := NewHumanReadablePrinter(false, false)
printer := NewHumanReadablePrinter(false, false, []string{})
printer.Handler(columns, ErrorPrintHandler)
obj := TestPrintType{"test object"}
buffer := &bytes.Buffer{}
@ -276,7 +276,7 @@ func TestPrintHandlerError(t *testing.T) {
}
func TestUnknownTypePrinting(t *testing.T) {
printer := NewHumanReadablePrinter(false, false)
printer := NewHumanReadablePrinter(false, false, []string{})
buffer := &bytes.Buffer{}
err := printer.PrintObj(&TestUnknownType{}, buffer)
if err == nil {
@ -452,8 +452,8 @@ func TestPrinters(t *testing.T) {
t.Fatal(err)
}
printers := map[string]ResourcePrinter{
"humanReadable": NewHumanReadablePrinter(true, false),
"humanReadableHeaders": NewHumanReadablePrinter(false, false),
"humanReadable": NewHumanReadablePrinter(true, false, []string{}),
"humanReadableHeaders": NewHumanReadablePrinter(false, false, []string{}),
"json": &JSONPrinter{},
"yaml": &YAMLPrinter{},
"template": templatePrinter,
@ -490,7 +490,7 @@ func TestPrinters(t *testing.T) {
func TestPrintEventsResultSorted(t *testing.T) {
// Arrange
printer := NewHumanReadablePrinter(false /* noHeaders */, false)
printer := NewHumanReadablePrinter(false /* noHeaders */, false, []string{})
obj := api.EventList{
Items: []api.Event{
@ -531,7 +531,7 @@ func TestPrintEventsResultSorted(t *testing.T) {
}
func TestPrintMinionStatus(t *testing.T) {
printer := NewHumanReadablePrinter(false, false)
printer := NewHumanReadablePrinter(false, false, []string{})
table := []struct {
minion api.Node
status string
@ -739,7 +739,7 @@ func TestPrintHumanReadableService(t *testing.T) {
for _, svc := range tests {
buff := bytes.Buffer{}
printService(&svc, &buff, false)
printService(&svc, &buff, false, []string{})
output := string(buff.Bytes())
ip := svc.Spec.ClusterIP
if !strings.Contains(output, ip) {
@ -921,7 +921,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
},
}
printer := NewHumanReadablePrinter(false, false)
printer := NewHumanReadablePrinter(false, false, []string{})
for _, test := range table {
buffer := &bytes.Buffer{}
err := printer.PrintObj(test.obj, buffer)
@ -934,7 +934,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
}
}
printer = NewHumanReadablePrinter(false, true)
printer = NewHumanReadablePrinter(false, true, []string{})
for _, test := range table {
buffer := &bytes.Buffer{}
err := printer.PrintObj(test.obj, buffer)
@ -1019,7 +1019,7 @@ func TestPrintPod(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
for _, test := range tests {
printPod(&test.pod, buf, false)
printPod(&test.pod, buf, false, []string{})
// We ignore time
if !strings.HasPrefix(buf.String(), test.expect) {
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String())
@ -1027,3 +1027,63 @@ func TestPrintPod(t *testing.T) {
buf.Reset()
}
}
func TestPrintPodWithLabels(t *testing.T) {
tests := []struct {
pod api.Pod
labelColumns []string
startsWith string
endsWith string
}{
{
// Test name, num of containers, restarts, container ready status
api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "test1",
Labels: map[string]string{"col1": "asd", "COL2": "zxc"},
},
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
[]string{"col1", "COL2"},
"test1\t1/2\tpodPhase\t6\t",
"\tasd\tzxc\n",
},
{
// Test name, num of containers, restarts, container ready status
api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "test1",
Labels: map[string]string{"col1": "asd", "COL2": "zxc"},
},
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
[]string{},
"test1\t1/2\tpodPhase\t6\t",
"\n",
},
}
buf := bytes.NewBuffer([]byte{})
for _, test := range tests {
printPod(&test.pod, buf, false, test.labelColumns)
// We ignore time
if !strings.HasPrefix(buf.String(), test.startsWith) || !strings.HasSuffix(buf.String(), test.endsWith) {
t.Fatalf("Expected to start with: %s and end with: %s, but got: %s", test.startsWith, test.endsWith, buf.String())
}
buf.Reset()
}
}