...

Source file src/sigs.k8s.io/cli-utils/pkg/print/table/columndefs.go

Documentation: sigs.k8s.io/cli-utils/pkg/print/table

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package table
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"time"
    10  
    11  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    12  	"k8s.io/utils/integer"
    13  	"sigs.k8s.io/cli-utils/pkg/print/common"
    14  )
    15  
    16  // ColumnDef is an implementation of the ColumnDefinition interface.
    17  // It can be used to define simple columns that doesn't need additional
    18  // knowledge about the actual type of the provided Resource besides the
    19  // information available through the interface.
    20  type ColumnDef struct {
    21  	ColumnName        string
    22  	ColumnHeader      string
    23  	ColumnWidth       int
    24  	PrintResourceFunc func(w io.Writer, width int, r Resource) (int, error)
    25  }
    26  
    27  // Name returns the name of the column.
    28  func (c ColumnDef) Name() string {
    29  	return c.ColumnName
    30  }
    31  
    32  // Header returns the header that should be printed for
    33  // the column.
    34  func (c ColumnDef) Header() string {
    35  	return c.ColumnHeader
    36  }
    37  
    38  // Width returns the width of the column.
    39  func (c ColumnDef) Width() int {
    40  	return c.ColumnWidth
    41  }
    42  
    43  // PrintResource is called by the BaseTablePrinter to output the
    44  // content of a particular column. This implementation just delegates
    45  // to the provided PrintResourceFunc.
    46  func (c ColumnDef) PrintResource(w io.Writer, width int, r Resource) (int, error) {
    47  	return c.PrintResourceFunc(w, width, r)
    48  }
    49  
    50  // MustColumn returns the pre-defined column definition with the
    51  // provided name. If the name doesn't exist, it will panic.
    52  func MustColumn(name string) ColumnDef {
    53  	c, found := columnDefinitions[name]
    54  	if !found {
    55  		panic(fmt.Errorf("unknown column name %q", name))
    56  	}
    57  	return c
    58  }
    59  
    60  var (
    61  	columnDefinitions = map[string]ColumnDef{
    62  		// namespace defines a column that output the namespace of the
    63  		// resource, or nothing in the case of clusterscoped resources.
    64  		"namespace": {
    65  			ColumnName:   "namespace",
    66  			ColumnHeader: "NAMESPACE",
    67  			ColumnWidth:  10,
    68  			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
    69  				error) {
    70  				namespace := r.Identifier().Namespace
    71  				if len(namespace) > width {
    72  					namespace = namespace[:width]
    73  				}
    74  				_, err := fmt.Fprint(w, namespace)
    75  				return len(namespace), err
    76  			},
    77  		},
    78  		// resource defines a column that outputs the kind and name of a
    79  		// resource.
    80  		"resource": {
    81  			ColumnName:   "resource",
    82  			ColumnHeader: "RESOURCE",
    83  			ColumnWidth:  40,
    84  			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
    85  				error) {
    86  				text := fmt.Sprintf("%s/%s", r.Identifier().GroupKind.Kind,
    87  					r.Identifier().Name)
    88  				if len(text) > width {
    89  					text = text[:width]
    90  				}
    91  				_, err := fmt.Fprint(w, text)
    92  				return len(text), err
    93  			},
    94  		},
    95  		// status defines a column that outputs the status of a resource. It
    96  		// will use ansii escape codes to color the output.
    97  		"status": {
    98  			ColumnName:   "status",
    99  			ColumnHeader: "STATUS",
   100  			ColumnWidth:  10,
   101  			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
   102  				error) {
   103  				rs := r.ResourceStatus()
   104  				if rs == nil {
   105  					return 0, nil
   106  				}
   107  				s := rs.Status.String()
   108  				if len(s) > width {
   109  					s = s[:width]
   110  				}
   111  				color, setColor := common.ColorForStatus(rs.Status)
   112  				var outputStatus string
   113  				if setColor {
   114  					outputStatus = common.SprintfWithColor(color, s)
   115  				} else {
   116  					outputStatus = s
   117  				}
   118  				_, err := fmt.Fprint(w, outputStatus)
   119  				return len(s), err
   120  			},
   121  		},
   122  		// conditions defines a column that outputs the conditions for
   123  		// a resource. The output will be in colors.
   124  		"conditions": {
   125  			ColumnName:   "conditions",
   126  			ColumnHeader: "CONDITIONS",
   127  			ColumnWidth:  40,
   128  			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
   129  				error) {
   130  				rs := r.ResourceStatus()
   131  				if rs == nil {
   132  					return 0, nil
   133  				}
   134  				u := rs.Resource
   135  				if u == nil {
   136  					return fmt.Fprintf(w, "-")
   137  				}
   138  
   139  				conditions, found, err := unstructured.NestedSlice(u.Object,
   140  					"status", "conditions")
   141  				if !found || err != nil || len(conditions) == 0 {
   142  					return fmt.Fprintf(w, "<None>")
   143  				}
   144  
   145  				realLength := 0
   146  				for i, cond := range conditions {
   147  					condition := cond.(map[string]interface{})
   148  					conditionType := condition["type"].(string)
   149  					conditionStatus := condition["status"].(string)
   150  					var color common.Color
   151  					switch conditionStatus {
   152  					case "True":
   153  						color = common.GREEN
   154  					case "False":
   155  						color = common.RED
   156  					default:
   157  						color = common.YELLOW
   158  					}
   159  					remainingWidth := width - realLength
   160  					if len(conditionType) > remainingWidth {
   161  						conditionType = conditionType[:remainingWidth]
   162  					}
   163  					_, err := fmt.Fprint(w, common.SprintfWithColor(color, conditionType))
   164  					if err != nil {
   165  						return realLength, err
   166  					}
   167  					realLength += len(conditionType)
   168  					if i < len(conditions)-1 && width-realLength > 2 {
   169  						_, err = fmt.Fprintf(w, ",")
   170  						if err != nil {
   171  							return realLength, err
   172  						}
   173  						realLength++
   174  					}
   175  				}
   176  				return realLength, nil
   177  			},
   178  		},
   179  		// age defines a column that outputs the age of a resource computed
   180  		// by looking at the creationTimestamp field.
   181  		"age": {
   182  			ColumnName:   "age",
   183  			ColumnHeader: "AGE",
   184  			ColumnWidth:  6,
   185  			PrintResourceFunc: func(w io.Writer, width int, r Resource) (i int, err error) {
   186  				rs := r.ResourceStatus()
   187  				if rs == nil {
   188  					return 0, nil
   189  				}
   190  				u := rs.Resource
   191  				if u == nil {
   192  					return fmt.Fprint(w, "-")
   193  				}
   194  
   195  				timestamp, found, err := unstructured.NestedString(u.Object,
   196  					"metadata", "creationTimestamp")
   197  				if !found || err != nil || timestamp == "" {
   198  					return fmt.Fprint(w, "-")
   199  				}
   200  				parsedTime, err := time.Parse(time.RFC3339, timestamp)
   201  				if err != nil {
   202  					return fmt.Fprint(w, "-")
   203  				}
   204  				age := time.Since(parsedTime)
   205  				switch {
   206  				case age.Seconds() <= 90:
   207  					return fmt.Fprintf(w, "%ds",
   208  						integer.RoundToInt32(age.Round(time.Second).Seconds()))
   209  				case age.Minutes() <= 90:
   210  					return fmt.Fprintf(w, "%dm",
   211  						integer.RoundToInt32(age.Round(time.Minute).Minutes()))
   212  				default:
   213  					return fmt.Fprintf(w, "%dh",
   214  						integer.RoundToInt32(age.Round(time.Hour).Hours()))
   215  				}
   216  			},
   217  		},
   218  		// message defines a column that outputs the message from a
   219  		// ResourceStatus, or if there is a non-nil error, output the text
   220  		// from the error instead.
   221  		"message": {
   222  			ColumnName:   "message",
   223  			ColumnHeader: "MESSAGE",
   224  			ColumnWidth:  40,
   225  			PrintResourceFunc: func(w io.Writer, width int, r Resource) (i int, err error) {
   226  				rs := r.ResourceStatus()
   227  				if rs == nil {
   228  					return 0, nil
   229  				}
   230  				var message string
   231  				if rs.Error != nil {
   232  					message = rs.Error.Error()
   233  				} else {
   234  					message = rs.Message
   235  				}
   236  				if len(message) > width {
   237  					message = message[:width]
   238  				}
   239  				return fmt.Fprint(w, message)
   240  			},
   241  		},
   242  	}
   243  )
   244  

View as plain text