mirror of https://github.com/k3s-io/k3s
Refactor printers to support rendering as a Table
Return tables from the server.pull/6/head
parent
f203e42cb9
commit
7ce63eb608
|
@ -192,7 +192,7 @@ func Example_printReplicationControllerWithNamespace() {
|
|||
},
|
||||
}
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
|
||||
err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ func Example_printMultiContainersReplicationControllerWithWide() {
|
|||
},
|
||||
}
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
|
||||
err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ func Example_printReplicationController() {
|
|||
},
|
||||
}
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
|
||||
err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ func Example_printPodWithWideFormat() {
|
|||
},
|
||||
}
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
|
||||
err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -390,7 +390,7 @@ func Example_printPodWithShowLabels() {
|
|||
},
|
||||
}
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
|
||||
err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -514,7 +514,7 @@ func Example_printPodHideTerminated() {
|
|||
}
|
||||
for _, pod := range filteredPodList {
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
|
||||
err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ func Example_printPodShowAll() {
|
|||
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
|
||||
podList := newAllPhasePodList()
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, podList, os.Stdout)
|
||||
err := f.PrintObject(cmd, mapper, podList, printers.GetNewTabWriter(os.Stdout))
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -616,9 +616,10 @@ func Example_printServiceWithNamespacesAndLabels() {
|
|||
}
|
||||
ld := strings.NewLineDelimiter(os.Stdout, "|")
|
||||
defer ld.Flush()
|
||||
|
||||
out := printers.GetNewTabWriter(ld)
|
||||
defer out.Flush()
|
||||
mapper, _ := f.Object()
|
||||
err := f.PrintObject(cmd, mapper, svc, ld)
|
||||
err := f.PrintObject(cmd, mapper, svc, out)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
@ -28,12 +28,14 @@ go_library(
|
|||
"//pkg/util/slice:go_default_library",
|
||||
"//vendor/github.com/fatih/camelcase:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -63,6 +65,7 @@ filegroup(
|
|||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/printers/internalversion:all-srcs",
|
||||
"//pkg/printers/storage:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
|
|
@ -26,22 +26,33 @@ import (
|
|||
"text/tabwriter"
|
||||
|
||||
"github.com/fatih/camelcase"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"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"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/slice"
|
||||
)
|
||||
|
||||
type TablePrinter interface {
|
||||
PrintTable(obj runtime.Object, options PrintOptions) (*metav1alpha1.Table, error)
|
||||
}
|
||||
|
||||
type PrintHandler interface {
|
||||
Handler(columns, columnsWithWide []string, printFunc interface{}) error
|
||||
TableHandler(columns []metav1alpha1.TableColumnDefinition, printFunc interface{}) error
|
||||
}
|
||||
|
||||
var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
|
||||
|
||||
type handlerEntry struct {
|
||||
columns []string
|
||||
columnsWithWide []string
|
||||
printFunc reflect.Value
|
||||
args []reflect.Value
|
||||
columnDefinitions []metav1alpha1.TableColumnDefinition
|
||||
printRows bool
|
||||
printFunc reflect.Value
|
||||
args []reflect.Value
|
||||
}
|
||||
|
||||
// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide
|
||||
|
@ -57,6 +68,8 @@ type HumanReadablePrinter struct {
|
|||
decoder runtime.Decoder
|
||||
}
|
||||
|
||||
var _ PrintHandler = &HumanReadablePrinter{}
|
||||
|
||||
// NewHumanReadablePrinter creates a HumanReadablePrinter.
|
||||
// If encoder and decoder are provided, an attempt to convert unstructured types to internal types is made.
|
||||
func NewHumanReadablePrinter(encoder runtime.Encoder, decoder runtime.Decoder, options PrintOptions) *HumanReadablePrinter {
|
||||
|
@ -69,6 +82,20 @@ func NewHumanReadablePrinter(encoder runtime.Encoder, decoder runtime.Decoder, o
|
|||
return printer
|
||||
}
|
||||
|
||||
// NewTablePrinter creates a HumanReadablePrinter suitable for calling PrintTable().
|
||||
func NewTablePrinter() *HumanReadablePrinter {
|
||||
return &HumanReadablePrinter{
|
||||
handlerMap: make(map[reflect.Type]*handlerEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePrinter {
|
||||
for _, fn := range fns {
|
||||
fn(a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// GetResourceKind returns the type currently set for a resource
|
||||
func (h *HumanReadablePrinter) GetResourceKind() string {
|
||||
return h.options.Kind
|
||||
|
@ -92,29 +119,100 @@ func (h *HumanReadablePrinter) EnsurePrintHeaders() {
|
|||
}
|
||||
|
||||
// Handler adds a print handler with a given set of columns to HumanReadablePrinter instance.
|
||||
// See validatePrintHandlerFunc for required method signature.
|
||||
// See ValidatePrintHandlerFunc for required method signature.
|
||||
func (h *HumanReadablePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error {
|
||||
var columnDefinitions []metav1alpha1.TableColumnDefinition
|
||||
for _, column := range columns {
|
||||
columnDefinitions = append(columnDefinitions, metav1alpha1.TableColumnDefinition{
|
||||
Name: column,
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
for _, column := range columnsWithWide {
|
||||
columnDefinitions = append(columnDefinitions, metav1alpha1.TableColumnDefinition{
|
||||
Name: column,
|
||||
Type: "string",
|
||||
Priority: 1,
|
||||
})
|
||||
}
|
||||
|
||||
printFuncValue := reflect.ValueOf(printFunc)
|
||||
if err := h.validatePrintHandlerFunc(printFuncValue); err != nil {
|
||||
glog.Errorf("Unable to add print handler: %v", err)
|
||||
if err := ValidatePrintHandlerFunc(printFuncValue); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
entry := &handlerEntry{
|
||||
columnDefinitions: columnDefinitions,
|
||||
printFunc: printFuncValue,
|
||||
}
|
||||
|
||||
objType := printFuncValue.Type().In(0)
|
||||
h.handlerMap[objType] = &handlerEntry{
|
||||
columns: columns,
|
||||
columnsWithWide: columnsWithWide,
|
||||
printFunc: printFuncValue,
|
||||
if _, ok := h.handlerMap[objType]; ok {
|
||||
err := fmt.Errorf("registered duplicate printer for %v", objType)
|
||||
utilruntime.HandleError(err)
|
||||
return err
|
||||
}
|
||||
h.handlerMap[objType] = entry
|
||||
return nil
|
||||
}
|
||||
|
||||
// TableHandler adds a print handler with a given set of columns to HumanReadablePrinter instance.
|
||||
// See ValidateRowPrintHandlerFunc for required method signature.
|
||||
func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1alpha1.TableColumnDefinition, printFunc interface{}) error {
|
||||
printFuncValue := reflect.ValueOf(printFunc)
|
||||
if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
|
||||
return err
|
||||
}
|
||||
entry := &handlerEntry{
|
||||
columnDefinitions: columnDefinitions,
|
||||
printRows: true,
|
||||
printFunc: printFuncValue,
|
||||
}
|
||||
|
||||
objType := printFuncValue.Type().In(0)
|
||||
if _, ok := h.handlerMap[objType]; ok {
|
||||
err := fmt.Errorf("registered duplicate printer for %v", objType)
|
||||
utilruntime.HandleError(err)
|
||||
return err
|
||||
}
|
||||
h.handlerMap[objType] = entry
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateRowPrintHandlerFunc validates print handler signature.
|
||||
// printFunc is the function that will be called to print an object.
|
||||
// It must be of the following type:
|
||||
// func printFunc(object ObjectType, options PrintOptions) ([]metav1alpha1.TableRow, error)
|
||||
// where ObjectType is the type of the object that will be printed, and the first
|
||||
// return value is an array of rows, with each row containing a number of cells that
|
||||
// match the number of coulmns defined for that printer function.
|
||||
func ValidateRowPrintHandlerFunc(printFunc reflect.Value) error {
|
||||
if printFunc.Kind() != reflect.Func {
|
||||
return fmt.Errorf("invalid print handler. %#v is not a function", printFunc)
|
||||
}
|
||||
funcType := printFunc.Type()
|
||||
if funcType.NumIn() != 2 || funcType.NumOut() != 2 {
|
||||
return fmt.Errorf("invalid print handler." +
|
||||
"Must accept 2 parameters and return 2 value.")
|
||||
}
|
||||
if funcType.In(1) != reflect.TypeOf((*PrintOptions)(nil)).Elem() ||
|
||||
funcType.Out(0) != reflect.TypeOf((*[]metav1alpha1.TableRow)(nil)).Elem() ||
|
||||
funcType.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
||||
return fmt.Errorf("invalid print handler. The expected signature is: "+
|
||||
"func handler(obj %v, options PrintOptions) ([]metav1alpha1.TableRow, error)", funcType.In(0))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePrintHandlerFunc validates print handler signature.
|
||||
// ValidatePrintHandlerFunc validates print handler signature.
|
||||
// printFunc is the function that will be called to print an object.
|
||||
// It must be of the following type:
|
||||
// func printFunc(object ObjectType, w io.Writer, options PrintOptions) error
|
||||
// where ObjectType is the type of the object that will be printed.
|
||||
func (h *HumanReadablePrinter) validatePrintHandlerFunc(printFunc reflect.Value) error {
|
||||
// DEPRECATED: will be replaced with ValidateRowPrintHandlerFunc
|
||||
func ValidatePrintHandlerFunc(printFunc reflect.Value) error {
|
||||
if printFunc.Kind() != reflect.Func {
|
||||
return fmt.Errorf("invalid print handler. %#v is not a function", printFunc)
|
||||
}
|
||||
|
@ -167,12 +265,18 @@ func (h *HumanReadablePrinter) printHeader(columnNames []string, w io.Writer) er
|
|||
// 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 {
|
||||
// if output is a tabwriter (when it's called by kubectl get), we use it; create a new tabwriter otherwise
|
||||
w, found := output.(*tabwriter.Writer)
|
||||
if !found {
|
||||
w = GetNewTabWriter(output)
|
||||
if w, found := output.(*tabwriter.Writer); found {
|
||||
defer w.Flush()
|
||||
}
|
||||
|
||||
// display tables following the rules of options
|
||||
if table, ok := obj.(*metav1alpha1.Table); ok {
|
||||
if err := DecorateTable(table, h.options); err != nil {
|
||||
return err
|
||||
}
|
||||
return PrintTable(table, output, h.options)
|
||||
}
|
||||
|
||||
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before
|
||||
// trying to print, since the printers are keyed by type. This is extremely expensive.
|
||||
if h.encoder != nil && h.decoder != nil {
|
||||
|
@ -182,9 +286,12 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
|
|||
t := reflect.TypeOf(obj)
|
||||
if handler := h.handlerMap[t]; handler != nil {
|
||||
if !h.options.NoHeaders && t != h.lastType {
|
||||
headers := handler.columns
|
||||
if h.options.Wide {
|
||||
headers = append(headers, handler.columnsWithWide...)
|
||||
var headers []string
|
||||
for _, column := range handler.columnDefinitions {
|
||||
if column.Priority != 0 && !h.options.Wide {
|
||||
continue
|
||||
}
|
||||
headers = append(headers, strings.ToUpper(column.Name))
|
||||
}
|
||||
headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...)
|
||||
// LABELS is always the last column.
|
||||
|
@ -192,10 +299,58 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
|
|||
if h.options.WithNamespace {
|
||||
headers = append(withNamespacePrefixColumns, headers...)
|
||||
}
|
||||
h.printHeader(headers, w)
|
||||
h.printHeader(headers, output)
|
||||
h.lastType = t
|
||||
}
|
||||
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(w), reflect.ValueOf(h.options)}
|
||||
|
||||
if handler.printRows {
|
||||
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(h.options)}
|
||||
results := handler.printFunc.Call(args)
|
||||
if results[1].IsNil() {
|
||||
rows := results[0].Interface().([]metav1alpha1.TableRow)
|
||||
for _, row := range rows {
|
||||
|
||||
if h.options.WithNamespace {
|
||||
if obj := row.Object.Object; obj != nil {
|
||||
if m, err := meta.Accessor(obj); err == nil {
|
||||
fmt.Fprint(output, m.GetNamespace())
|
||||
}
|
||||
}
|
||||
fmt.Fprint(output, "\t")
|
||||
}
|
||||
|
||||
for i, cell := range row.Cells {
|
||||
if i != 0 {
|
||||
fmt.Fprint(output, "\t")
|
||||
} else {
|
||||
// TODO: remove this once we drop the legacy printers
|
||||
if h.options.WithKind && len(h.options.Kind) > 0 {
|
||||
fmt.Fprintf(output, "%s/%s", h.options.Kind, cell)
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Fprint(output, cell)
|
||||
}
|
||||
|
||||
hasLabels := len(h.options.ColumnLabels) > 0
|
||||
if obj := row.Object.Object; obj != nil && (hasLabels || h.options.ShowLabels) {
|
||||
if m, err := meta.Accessor(obj); err == nil {
|
||||
for _, value := range labelValues(m.GetLabels(), h.options) {
|
||||
output.Write([]byte("\t"))
|
||||
output.Write([]byte(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return results[1].Interface().(error)
|
||||
}
|
||||
|
||||
// TODO: this code path is deprecated and will be removed when all handlers are row printers
|
||||
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(output), reflect.ValueOf(h.options)}
|
||||
resultValue := handler.printFunc.Call(args)[0]
|
||||
if resultValue.IsNil() {
|
||||
return nil
|
||||
|
@ -207,7 +362,7 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
|
|||
// we don't recognize this type, but we can still attempt to print some reasonable information about.
|
||||
unstructured, ok := obj.(runtime.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("error: unknown type %#v", obj)
|
||||
return fmt.Errorf("error: unknown type %T, expected unstructured in %#v", obj, h.handlerMap)
|
||||
}
|
||||
|
||||
content := unstructured.UnstructuredContent()
|
||||
|
@ -255,12 +410,12 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
|
|||
if h.options.WithNamespace {
|
||||
headers = append(withNamespacePrefixColumns, headers...)
|
||||
}
|
||||
h.printHeader(headers, w)
|
||||
h.printHeader(headers, output)
|
||||
h.lastType = t
|
||||
}
|
||||
|
||||
// if the error isn't nil, report the "I don't recognize this" error
|
||||
if err := printUnstructured(unstructured, w, discoveredFieldNames, h.options); err != nil {
|
||||
if err := printUnstructured(unstructured, output, discoveredFieldNames, h.options); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -270,6 +425,250 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
|
|||
return fmt.Errorf("error: unknown type %#v", obj)
|
||||
}
|
||||
|
||||
func hasCondition(conditions []metav1alpha1.TableRowCondition, t metav1alpha1.RowConditionType) bool {
|
||||
for _, condition := range conditions {
|
||||
if condition.Type == t {
|
||||
return condition.Status == metav1alpha1.ConditionTrue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PrintTable prints a table to the provided output respecting the filtering rules for options
|
||||
// for wide columns and filetred rows. It filters out rows that are Completed. You should call
|
||||
// DecorateTable if you receive a table from a remote server before calling PrintTable.
|
||||
func PrintTable(table *metav1alpha1.Table, output io.Writer, options PrintOptions) error {
|
||||
if !options.NoHeaders {
|
||||
first := true
|
||||
for _, column := range table.ColumnDefinitions {
|
||||
if !options.Wide && column.Priority != 0 {
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
fmt.Fprint(output, "\t")
|
||||
}
|
||||
fmt.Fprint(output, strings.ToUpper(column.Name))
|
||||
}
|
||||
fmt.Fprintln(output)
|
||||
}
|
||||
for _, row := range table.Rows {
|
||||
if !options.ShowAll && hasCondition(row.Conditions, metav1alpha1.RowCompleted) {
|
||||
continue
|
||||
}
|
||||
first := true
|
||||
for i, cell := range row.Cells {
|
||||
column := table.ColumnDefinitions[i]
|
||||
if !options.Wide && column.Priority != 0 {
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
fmt.Fprint(output, "\t")
|
||||
}
|
||||
if cell != nil {
|
||||
fmt.Fprint(output, cell)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecorateTable takes a table and attempts to add label columns and the
|
||||
// namespace column. It will fill empty columns with nil (if the object
|
||||
// does not expose metadata). It returns an error if the table cannot
|
||||
// be decorated.
|
||||
func DecorateTable(table *metav1alpha1.Table, options PrintOptions) error {
|
||||
width := len(table.ColumnDefinitions) + len(options.ColumnLabels)
|
||||
if options.WithNamespace {
|
||||
width++
|
||||
}
|
||||
if options.ShowLabels {
|
||||
width++
|
||||
}
|
||||
|
||||
columns := table.ColumnDefinitions
|
||||
|
||||
nameColumn := -1
|
||||
if options.WithKind && len(options.Kind) > 0 {
|
||||
for i := range columns {
|
||||
if columns[i].Format == "name" && columns[i].Type == "string" {
|
||||
nameColumn = i
|
||||
fmt.Printf("found name column: %d\n", i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if width != len(table.ColumnDefinitions) {
|
||||
columns = make([]metav1alpha1.TableColumnDefinition, 0, width)
|
||||
if options.WithNamespace {
|
||||
columns = append(columns, metav1alpha1.TableColumnDefinition{
|
||||
Name: "Namespace",
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
columns = append(columns, table.ColumnDefinitions...)
|
||||
for _, label := range formatLabelHeaders(options.ColumnLabels) {
|
||||
columns = append(columns, metav1alpha1.TableColumnDefinition{
|
||||
Name: label,
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
if options.ShowLabels {
|
||||
columns = append(columns, metav1alpha1.TableColumnDefinition{
|
||||
Name: "Labels",
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rows := table.Rows
|
||||
|
||||
includeLabels := len(options.ColumnLabels) > 0 || options.ShowLabels
|
||||
if includeLabels || options.WithNamespace || nameColumn != -1 {
|
||||
for i := range rows {
|
||||
row := rows[i]
|
||||
|
||||
if nameColumn != -1 {
|
||||
row.Cells[nameColumn] = fmt.Sprintf("%s/%s", options.Kind, row.Cells[nameColumn])
|
||||
}
|
||||
|
||||
var m metav1.Object
|
||||
if obj := row.Object.Object; obj != nil {
|
||||
if acc, err := meta.Accessor(obj); err == nil {
|
||||
m = acc
|
||||
}
|
||||
}
|
||||
// if we can't get an accessor, fill out the appropriate columns with empty spaces
|
||||
if m == nil {
|
||||
if options.WithNamespace {
|
||||
r := make([]interface{}, 1, width)
|
||||
row.Cells = append(r, row.Cells...)
|
||||
}
|
||||
for j := 0; j < width-len(row.Cells); j++ {
|
||||
row.Cells = append(row.Cells, nil)
|
||||
}
|
||||
rows[i] = row
|
||||
continue
|
||||
}
|
||||
|
||||
if options.WithNamespace {
|
||||
r := make([]interface{}, 1, width)
|
||||
r[0] = m.GetNamespace()
|
||||
row.Cells = append(r, row.Cells...)
|
||||
}
|
||||
if includeLabels {
|
||||
row.Cells = appendLabelCells(row.Cells, m.GetLabels(), options)
|
||||
}
|
||||
rows[i] = row
|
||||
}
|
||||
}
|
||||
|
||||
table.ColumnDefinitions = columns
|
||||
table.Rows = rows
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintTable returns a table for the provided object, using the printer registered for that type. It returns
|
||||
// a table that includes all of the information requested by options, but will not remove rows or columns. The
|
||||
// caller is responsible for applying rules related to filtering rows or columns.
|
||||
func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptions) (*metav1alpha1.Table, error) {
|
||||
t := reflect.TypeOf(obj)
|
||||
handler, ok := h.handlerMap[t]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no table handler registered for this type %v", t)
|
||||
}
|
||||
if !handler.printRows {
|
||||
return h.legacyPrinterToTable(obj, handler)
|
||||
}
|
||||
|
||||
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
|
||||
results := handler.printFunc.Call(args)
|
||||
if !results[1].IsNil() {
|
||||
return nil, results[1].Interface().(error)
|
||||
}
|
||||
|
||||
columns := handler.columnDefinitions
|
||||
if !options.Wide {
|
||||
columns = make([]metav1alpha1.TableColumnDefinition, 0, len(handler.columnDefinitions))
|
||||
for i := range handler.columnDefinitions {
|
||||
if handler.columnDefinitions[i].Priority != 0 {
|
||||
continue
|
||||
}
|
||||
columns = append(columns, handler.columnDefinitions[i])
|
||||
}
|
||||
}
|
||||
table := &metav1alpha1.Table{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "",
|
||||
},
|
||||
ColumnDefinitions: columns,
|
||||
Rows: results[0].Interface().([]metav1alpha1.TableRow),
|
||||
}
|
||||
if err := DecorateTable(table, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
// legacyPrinterToTable uses the old printFunc with tabbed writer to generate a table.
|
||||
// TODO: remove when all legacy printers are removed.
|
||||
func (h *HumanReadablePrinter) legacyPrinterToTable(obj runtime.Object, handler *handlerEntry) (*metav1alpha1.Table, error) {
|
||||
printFunc := handler.printFunc
|
||||
table := &metav1alpha1.Table{
|
||||
ColumnDefinitions: handler.columnDefinitions,
|
||||
}
|
||||
|
||||
options := PrintOptions{
|
||||
NoHeaders: true,
|
||||
Wide: true,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(buf), reflect.ValueOf(options)}
|
||||
|
||||
if meta.IsListType(obj) {
|
||||
// TODO: this uses more memory than it has to, as we refactor printers we should remove the need
|
||||
// for this.
|
||||
args[0] = reflect.ValueOf(obj)
|
||||
resultValue := printFunc.Call(args)[0]
|
||||
if !resultValue.IsNil() {
|
||||
return nil, resultValue.Interface().(error)
|
||||
}
|
||||
data := buf.Bytes()
|
||||
i := 0
|
||||
items, err := meta.ExtractList(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for len(data) > 0 {
|
||||
cells, remainder := tabbedLineToCells(data, len(table.ColumnDefinitions))
|
||||
table.Rows = append(table.Rows, metav1alpha1.TableRow{
|
||||
Cells: cells,
|
||||
Object: runtime.RawExtension{Object: items[i]},
|
||||
})
|
||||
data = remainder
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
args[0] = reflect.ValueOf(obj)
|
||||
resultValue := printFunc.Call(args)[0]
|
||||
if !resultValue.IsNil() {
|
||||
return nil, resultValue.Interface().(error)
|
||||
}
|
||||
data := buf.Bytes()
|
||||
cells, _ := tabbedLineToCells(data, len(table.ColumnDefinitions))
|
||||
table.Rows = append(table.Rows, metav1alpha1.TableRow{
|
||||
Cells: cells,
|
||||
Object: runtime.RawExtension{Object: obj},
|
||||
})
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
// TODO: this method assumes the meta/v1 server API, so should be refactored out of this package
|
||||
func printUnstructured(unstructured runtime.Unstructured, w io.Writer, additionalFields []string, options PrintOptions) error {
|
||||
metadata, err := meta.Accessor(unstructured)
|
||||
|
@ -349,6 +748,30 @@ func formatShowLabelsHeader(showLabels bool, t reflect.Type) []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
// labelValues returns a slice of value columns matching the requested print options.
|
||||
func labelValues(itemLabels map[string]string, opts PrintOptions) []string {
|
||||
var values []string
|
||||
for _, key := range opts.ColumnLabels {
|
||||
values = append(values, itemLabels[key])
|
||||
}
|
||||
if opts.ShowLabels {
|
||||
values = append(values, labels.FormatLabels(itemLabels))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// appendLabelCells returns a slice of value columns matching the requested print options.
|
||||
// Intended for use with tables.
|
||||
func appendLabelCells(values []interface{}, itemLabels map[string]string, opts PrintOptions) []interface{} {
|
||||
for _, key := range opts.ColumnLabels {
|
||||
values = append(values, itemLabels[key])
|
||||
}
|
||||
if opts.ShowLabels {
|
||||
values = append(values, labels.FormatLabels(itemLabels))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// FormatResourceName receives a resource kind, name, and boolean specifying
|
||||
// whether or not to update the current name to "kind/name"
|
||||
func FormatResourceName(kind, name string, withKind bool) string {
|
||||
|
@ -402,3 +825,27 @@ func decodeUnknownObject(obj runtime.Object, encoder runtime.Encoder, decoder ru
|
|||
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func tabbedLineToCells(data []byte, expected int) ([]interface{}, []byte) {
|
||||
var remainder []byte
|
||||
max := bytes.Index(data, []byte("\n"))
|
||||
if max != -1 {
|
||||
remainder = data[max+1:]
|
||||
data = data[:max]
|
||||
}
|
||||
cells := make([]interface{}, expected)
|
||||
for i := 0; i < expected; i++ {
|
||||
next := bytes.Index(data, []byte("\t"))
|
||||
if next == -1 {
|
||||
cells[i] = string(data)
|
||||
// fill the remainder with empty strings, this indicates a printer bug
|
||||
for j := i + 1; j < expected; j++ {
|
||||
cells[j] = ""
|
||||
}
|
||||
break
|
||||
}
|
||||
cells[i] = string(data[:next])
|
||||
data = data[next+1:]
|
||||
}
|
||||
return cells, remainder
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ go_test(
|
|||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/yaml:go_default_library",
|
||||
|
@ -65,6 +66,7 @@ go_library(
|
|||
"//pkg/api/helper/qos:go_default_library",
|
||||
"//pkg/api/ref:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/apps:go_default_library",
|
||||
"//pkg/apis/autoscaling:go_default_library",
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
|
@ -94,8 +96,10 @@ go_library(
|
|||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
|
|
|
@ -27,12 +27,15 @@ import (
|
|||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/kubernetes/federation/apis/federation"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/events"
|
||||
"k8s.io/kubernetes/pkg/api/helper"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/apps"
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
|
@ -53,8 +56,6 @@ const loadBalancerWidth = 16
|
|||
// NOTE: When adding a new resource type here, please update the list
|
||||
// pkg/kubectl/cmd/get.go to reflect the new resource type.
|
||||
var (
|
||||
podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}
|
||||
podWideColumns = []string{"IP", "NODE"}
|
||||
podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"}
|
||||
podDisruptionBudgetColumns = []string{"NAME", "MIN-AVAILABLE", "MAX-UNAVAILABLE", "ALLOWED-DISRUPTIONS", "AGE"}
|
||||
replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"}
|
||||
|
@ -105,27 +106,21 @@ var (
|
|||
podPresetColumns = []string{"NAME", "AGE"}
|
||||
)
|
||||
|
||||
func printPod(pod *api.Pod, w io.Writer, options printers.PrintOptions) error {
|
||||
if err := printPodBase(pod, w, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printPodList(podList *api.PodList, w io.Writer, options printers.PrintOptions) error {
|
||||
for _, pod := range podList.Items {
|
||||
if err := printPodBase(&pod, w, options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddHandlers adds print handlers for default Kubernetes types dealing with internal versions.
|
||||
func AddHandlers(h *printers.HumanReadablePrinter) {
|
||||
h.Handler(podColumns, podWideColumns, printPodList)
|
||||
h.Handler(podColumns, podWideColumns, printPod)
|
||||
// TODO: handle errors from Handler
|
||||
func AddHandlers(h printers.PrintHandler) {
|
||||
podColumnDefinitions := []metav1alpha1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
|
||||
{Name: "Ready", Type: "string", Description: "The aggregate readiness state of this pod for accepting traffic."},
|
||||
{Name: "Status", Type: "string", Description: "The aggregate status of the containers in this pod."},
|
||||
{Name: "Restarts", Type: "integer", Description: "The number of times the containers in this pod have been restarted."},
|
||||
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
|
||||
{Name: "IP", Type: "string", Priority: 1, Description: apiv1.PodStatus{}.SwaggerDoc()["podIP"]},
|
||||
{Name: "Node", Type: "string", Priority: 1, Description: apiv1.PodSpec{}.SwaggerDoc()["nodeName"]},
|
||||
}
|
||||
h.TableHandler(podColumnDefinitions, printPodList)
|
||||
h.TableHandler(podColumnDefinitions, printPod)
|
||||
|
||||
h.Handler(podTemplateColumns, nil, printPodTemplate)
|
||||
h.Handler(podTemplateColumns, nil, printPodTemplateList)
|
||||
h.Handler(podDisruptionBudgetColumns, nil, printPodDisruptionBudget)
|
||||
|
@ -247,10 +242,24 @@ func translateTimestamp(timestamp metav1.Time) string {
|
|||
return printers.ShortHumanDuration(time.Now().Sub(timestamp.Time))
|
||||
}
|
||||
|
||||
func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) error {
|
||||
name := printers.FormatResourceName(options.Kind, pod.Name, options.WithKind)
|
||||
namespace := pod.Namespace
|
||||
var (
|
||||
podSuccessConditions = []metav1alpha1.TableRowCondition{{Type: metav1alpha1.RowCompleted, Status: metav1alpha1.ConditionTrue, Reason: string(api.PodSucceeded), Message: "The pod has completed successfully."}}
|
||||
podFailedConditions = []metav1alpha1.TableRowCondition{{Type: metav1alpha1.RowCompleted, Status: metav1alpha1.ConditionTrue, Reason: string(api.PodFailed), Message: "The pod failed."}}
|
||||
)
|
||||
|
||||
func printPodList(podList *api.PodList, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) {
|
||||
rows := make([]metav1alpha1.TableRow, 0, len(podList.Items))
|
||||
for i := range podList.Items {
|
||||
r, err := printPod(&podList.Items[i], options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows = append(rows, r...)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) {
|
||||
restarts := 0
|
||||
totalContainers := len(pod.Spec.Containers)
|
||||
readyContainers := 0
|
||||
|
@ -260,6 +269,17 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
|
|||
reason = pod.Status.Reason
|
||||
}
|
||||
|
||||
row := metav1alpha1.TableRow{
|
||||
Object: runtime.RawExtension{Object: pod},
|
||||
}
|
||||
|
||||
switch pod.Status.Phase {
|
||||
case api.PodSucceeded:
|
||||
row.Conditions = podSuccessConditions
|
||||
case api.PodFailed:
|
||||
row.Conditions = podFailedConditions
|
||||
}
|
||||
|
||||
initializing := false
|
||||
for i := range pod.Status.InitContainerStatuses {
|
||||
container := pod.Status.InitContainerStatuses[i]
|
||||
|
@ -316,21 +336,7 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
|
|||
reason = "Terminating"
|
||||
}
|
||||
|
||||
if options.WithNamespace {
|
||||
if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\t%d/%d\t%s\t%d\t%s",
|
||||
name,
|
||||
readyContainers,
|
||||
totalContainers,
|
||||
reason,
|
||||
restarts,
|
||||
translateTimestamp(pod.CreationTimestamp),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, restarts, translateTimestamp(pod.CreationTimestamp))
|
||||
|
||||
if options.Wide {
|
||||
nodeName := pod.Spec.NodeName
|
||||
|
@ -341,22 +347,10 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
|
|||
if nodeName == "" {
|
||||
nodeName = "<none>"
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "\t%s\t%s",
|
||||
podIP,
|
||||
nodeName,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
row.Cells = append(row.Cells, podIP, nodeName)
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(w, printers.AppendLabels(pod.Labels, options.ColumnLabels)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, pod.Labels)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return []metav1alpha1.TableRow{row}, nil
|
||||
}
|
||||
|
||||
func printPodTemplate(pod *api.PodTemplate, w io.Writer, options printers.PrintOptions) error {
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -31,6 +32,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
||||
|
@ -267,7 +269,7 @@ func TestCustomTypePrinting(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("An error occurred printing the custom type: %#v", err)
|
||||
}
|
||||
expectedOutput := "Data\ntest object"
|
||||
expectedOutput := "DATA\ntest object"
|
||||
if buffer.String() != expectedOutput {
|
||||
t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String())
|
||||
}
|
||||
|
@ -285,7 +287,7 @@ func TestCustomTypePrintingWithKind(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("An error occurred printing the custom type: %#v", err)
|
||||
}
|
||||
expectedOutput := "Data\ntest/test object"
|
||||
expectedOutput := "DATA\ntest/test object"
|
||||
if buffer.String() != expectedOutput {
|
||||
t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String())
|
||||
}
|
||||
|
@ -1252,7 +1254,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
for i, test := range table {
|
||||
if test.isNamespaced {
|
||||
// Expect output to include namespace when requested.
|
||||
printer := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
|
||||
|
@ -1266,7 +1268,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
|
|||
}
|
||||
matched := contains(strings.Fields(buffer.String()), fmt.Sprintf("%s", namespaceName))
|
||||
if !matched {
|
||||
t.Errorf("Expect printing object to contain namespace: %#v", test.obj)
|
||||
t.Errorf("%d: Expect printing object to contain namespace: %#v", i, test.obj)
|
||||
}
|
||||
} else {
|
||||
// Expect error when trying to get all namespaces for un-namespaced object.
|
||||
|
@ -1282,10 +1284,96 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPrintPodTable(t *testing.T) {
|
||||
runningPod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}},
|
||||
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
|
||||
Status: api.PodStatus{
|
||||
Phase: "Running",
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
|
||||
{RestartCount: 3},
|
||||
},
|
||||
},
|
||||
}
|
||||
failedPod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test2", Labels: map[string]string{"b": "2"}},
|
||||
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
|
||||
Status: api.PodStatus{
|
||||
Phase: "Failed",
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
|
||||
{RestartCount: 3},
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
obj runtime.Object
|
||||
opts printers.PrintOptions
|
||||
expect string
|
||||
ignoreLegacy bool
|
||||
}{
|
||||
{
|
||||
obj: runningPod, opts: printers.PrintOptions{},
|
||||
expect: "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\ntest1\t1/2\tRunning\t6\t<unknown>\n",
|
||||
},
|
||||
{
|
||||
obj: runningPod, opts: printers.PrintOptions{WithKind: true, Kind: "pods"},
|
||||
expect: "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\npods/test1\t1/2\tRunning\t6\t<unknown>\n",
|
||||
},
|
||||
{
|
||||
obj: runningPod, opts: printers.PrintOptions{ShowLabels: true},
|
||||
expect: "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\tLABELS\ntest1\t1/2\tRunning\t6\t<unknown>\ta=1,b=2\n",
|
||||
},
|
||||
{
|
||||
obj: &api.PodList{Items: []api.Pod{*runningPod, *failedPod}}, opts: printers.PrintOptions{ShowAll: true, ColumnLabels: []string{"a"}},
|
||||
expect: "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\tA\ntest1\t1/2\tRunning\t6\t<unknown>\t1\ntest2\t1/2\tFailed\t6\t<unknown>\t\n",
|
||||
},
|
||||
{
|
||||
obj: runningPod, opts: printers.PrintOptions{NoHeaders: true},
|
||||
expect: "test1\t1/2\tRunning\t6\t<unknown>\n",
|
||||
},
|
||||
{
|
||||
obj: failedPod, opts: printers.PrintOptions{},
|
||||
expect: "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\n",
|
||||
ignoreLegacy: true, // filtering is not done by the printer in the legacy path
|
||||
},
|
||||
{
|
||||
obj: failedPod, opts: printers.PrintOptions{ShowAll: true},
|
||||
expect: "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\ntest2\t1/2\tFailed\t6\t<unknown>\n",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(test.obj, printers.PrintOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
p := printers.NewHumanReadablePrinter(nil, nil, test.opts).With(AddHandlers)
|
||||
if err := p.PrintObj(table, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.expect != buf.String() {
|
||||
t.Errorf("%d mismatch:\n%s\n%s", i, strconv.Quote(test.expect), strconv.Quote(buf.String()))
|
||||
}
|
||||
if test.ignoreLegacy {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := p.PrintObj(test.obj, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.expect != buf.String() {
|
||||
t.Errorf("%d legacy mismatch:\n%s\n%s", i, strconv.Quote(test.expect), strconv.Quote(buf.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestPrintPod(t *testing.T) {
|
||||
tests := []struct {
|
||||
pod api.Pod
|
||||
expect string
|
||||
expect []metav1alpha1.TableRow
|
||||
}{
|
||||
{
|
||||
// Test name, num of containers, restarts, container ready status
|
||||
|
@ -1300,7 +1388,7 @@ func TestPrintPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test1\t1/2\tpodPhase\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>"}}},
|
||||
},
|
||||
{
|
||||
// Test container error overwrites pod phase
|
||||
|
@ -1315,7 +1403,7 @@ func TestPrintPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test2\t1/2\tContainerWaitingReason\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", 6, "<unknown>"}}},
|
||||
},
|
||||
{
|
||||
// Test the same as the above but with Terminated state and the first container overwrites the rest
|
||||
|
@ -1330,7 +1418,7 @@ func TestPrintPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test3\t0/2\tContainerWaitingReason\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test3", "0/2", "ContainerWaitingReason", 6, "<unknown>"}}},
|
||||
},
|
||||
{
|
||||
// Test ready is not enough for reporting running
|
||||
|
@ -1345,7 +1433,7 @@ func TestPrintPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test4\t1/2\tpodPhase\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test4", "1/2", "podPhase", 6, "<unknown>"}}},
|
||||
},
|
||||
{
|
||||
// Test ready is not enough for reporting running
|
||||
|
@ -1361,25 +1449,28 @@ func TestPrintPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test5\t1/2\tOutOfDisk\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test5", "1/2", "OutOfDisk", 6, "<unknown>"}}},
|
||||
},
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
for _, test := range tests {
|
||||
printPod(&test.pod, buf, printers.PrintOptions{ShowAll: true})
|
||||
// We ignore time
|
||||
if !strings.HasPrefix(buf.String(), test.expect) {
|
||||
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String())
|
||||
for i, test := range tests {
|
||||
rows, err := printPod(&test.pod, printers.PrintOptions{ShowAll: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := range rows {
|
||||
rows[i].Object.Object = nil
|
||||
}
|
||||
if !reflect.DeepEqual(test.expect, rows) {
|
||||
t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expect, rows))
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintNonTerminatedPod(t *testing.T) {
|
||||
tests := []struct {
|
||||
pod api.Pod
|
||||
expect string
|
||||
expect []metav1alpha1.TableRow
|
||||
}{
|
||||
{
|
||||
// Test pod phase Running should be printed
|
||||
|
@ -1394,7 +1485,7 @@ func TestPrintNonTerminatedPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test1\t1/2\tRunning\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", 6, "<unknown>"}}},
|
||||
},
|
||||
{
|
||||
// Test pod phase Pending should be printed
|
||||
|
@ -1409,7 +1500,7 @@ func TestPrintNonTerminatedPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test2\t1/2\tPending\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test2", "1/2", "Pending", 6, "<unknown>"}}},
|
||||
},
|
||||
{
|
||||
// Test pod phase Unknown should be printed
|
||||
|
@ -1424,7 +1515,7 @@ func TestPrintNonTerminatedPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test3\t1/2\tUnknown\t6\t",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test3", "1/2", "Unknown", 6, "<unknown>"}}},
|
||||
},
|
||||
{
|
||||
// Test pod phase Succeeded shouldn't be printed
|
||||
|
@ -1439,7 +1530,7 @@ func TestPrintNonTerminatedPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test4", "1/2", "Succeeded", 6, "<unknown>"}, Conditions: podSuccessConditions}},
|
||||
},
|
||||
{
|
||||
// Test pod phase Failed shouldn't be printed
|
||||
|
@ -1454,18 +1545,22 @@ func TestPrintNonTerminatedPod(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test5", "1/2", "Failed", 6, "<unknown>"}, Conditions: podFailedConditions}},
|
||||
},
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
for _, test := range tests {
|
||||
printPod(&test.pod, buf, printers.PrintOptions{})
|
||||
// We ignore time
|
||||
if !strings.HasPrefix(buf.String(), test.expect) {
|
||||
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String())
|
||||
for i, test := range tests {
|
||||
table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.pod, printers.PrintOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows := table.Rows
|
||||
for i := range rows {
|
||||
rows[i].Object.Object = nil
|
||||
}
|
||||
if !reflect.DeepEqual(test.expect, rows) {
|
||||
t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expect, rows))
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1473,8 +1568,7 @@ func TestPrintPodWithLabels(t *testing.T) {
|
|||
tests := []struct {
|
||||
pod api.Pod
|
||||
labelColumns []string
|
||||
startsWith string
|
||||
endsWith string
|
||||
expect []metav1alpha1.TableRow
|
||||
}{
|
||||
{
|
||||
// Test name, num of containers, restarts, container ready status
|
||||
|
@ -1493,8 +1587,7 @@ func TestPrintPodWithLabels(t *testing.T) {
|
|||
},
|
||||
},
|
||||
[]string{"col1", "COL2"},
|
||||
"test1\t1/2\tpodPhase\t6\t",
|
||||
"\tasd\tzxc\n",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>", "asd", "zxc"}}},
|
||||
},
|
||||
{
|
||||
// Test name, num of containers, restarts, container ready status
|
||||
|
@ -1513,19 +1606,22 @@ func TestPrintPodWithLabels(t *testing.T) {
|
|||
},
|
||||
},
|
||||
[]string{},
|
||||
"test1\t1/2\tpodPhase\t6\t",
|
||||
"\n",
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>"}}},
|
||||
},
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
for _, test := range tests {
|
||||
printPod(&test.pod, buf, printers.PrintOptions{ColumnLabels: 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())
|
||||
for i, test := range tests {
|
||||
table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.pod, printers.PrintOptions{ColumnLabels: test.labelColumns})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows := table.Rows
|
||||
for i := range rows {
|
||||
rows[i].Object.Object = nil
|
||||
}
|
||||
if !reflect.DeepEqual(test.expect, rows) {
|
||||
t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expect, rows))
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2078,9 +2174,8 @@ func TestPrintHPA(t *testing.T) {
|
|||
func TestPrintPodShowLabels(t *testing.T) {
|
||||
tests := []struct {
|
||||
pod api.Pod
|
||||
startsWith string
|
||||
endsWith string
|
||||
showLabels bool
|
||||
expect []metav1alpha1.TableRow
|
||||
}{
|
||||
{
|
||||
// Test name, num of containers, restarts, container ready status
|
||||
|
@ -2098,9 +2193,8 @@ func TestPrintPodShowLabels(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test1\t1/2\tpodPhase\t6\t",
|
||||
"\tCOL2=zxc,col1=asd\n",
|
||||
true,
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>", "COL2=zxc,col1=asd"}}},
|
||||
},
|
||||
{
|
||||
// Test name, num of containers, restarts, container ready status
|
||||
|
@ -2118,20 +2212,23 @@ func TestPrintPodShowLabels(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"test1\t1/2\tpodPhase\t6\t",
|
||||
"\n",
|
||||
false,
|
||||
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>"}}},
|
||||
},
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
for _, test := range tests {
|
||||
printPod(&test.pod, buf, printers.PrintOptions{ShowLabels: test.showLabels})
|
||||
// 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())
|
||||
for i, test := range tests {
|
||||
table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.pod, printers.PrintOptions{ShowLabels: test.showLabels})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows := table.Rows
|
||||
for i := range rows {
|
||||
rows[i].Object.Object = nil
|
||||
}
|
||||
if !reflect.DeepEqual(test.expect, rows) {
|
||||
t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expect, rows))
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["storage.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/printers:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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 storage
|
||||
|
||||
import (
|
||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
type TableConvertor struct {
|
||||
printers.TablePrinter
|
||||
}
|
||||
|
||||
func (c TableConvertor) ConvertToTable(ctx genericapirequest.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error) {
|
||||
return c.TablePrinter.PrintTable(obj, printers.PrintOptions{Wide: true})
|
||||
}
|
|
@ -15,12 +15,14 @@ go_test(
|
|||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/registry/registrytest:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
|
@ -49,6 +51,9 @@ go_library(
|
|||
"//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:go_default_library",
|
||||
"//pkg/client/retry:go_default_library",
|
||||
"//pkg/kubelet/client:go_default_library",
|
||||
"//pkg/printers:go_default_library",
|
||||
"//pkg/printers/internalversion:go_default_library",
|
||||
"//pkg/printers/storage:go_default_library",
|
||||
"//pkg/registry/cachesize:go_default_library",
|
||||
"//pkg/registry/core/pod:go_default_library",
|
||||
"//pkg/registry/core/pod/rest:go_default_library",
|
||||
|
|
|
@ -35,6 +35,9 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
policyclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/cachesize"
|
||||
"k8s.io/kubernetes/pkg/registry/core/pod"
|
||||
podrest "k8s.io/kubernetes/pkg/registry/core/pod/rest"
|
||||
|
@ -61,6 +64,7 @@ type REST struct {
|
|||
|
||||
// NewStorage returns a RESTStorage object that will work against pods.
|
||||
func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) PodStorage {
|
||||
|
||||
store := &genericregistry.Store{
|
||||
Copier: api.Scheme,
|
||||
NewFunc: func() runtime.Object { return &api.Pod{} },
|
||||
|
@ -73,6 +77,8 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGet
|
|||
UpdateStrategy: pod.Strategy,
|
||||
DeleteStrategy: pod.Strategy,
|
||||
ReturnDeletedObject: true,
|
||||
|
||||
TableConvertor: printerstorage.TableConvertor{TablePrinter: printers.NewTablePrinter().With(printersinternal.AddHandlers)},
|
||||
}
|
||||
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: pod.GetAttrs, TriggerFunc: pod.NodeNameTriggerFunc}
|
||||
if err := store.CompleteWithOptions(options); err != nil {
|
||||
|
|
|
@ -19,11 +19,13 @@ package storage
|
|||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -35,6 +37,7 @@ import (
|
|||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
)
|
||||
|
@ -396,6 +399,88 @@ func TestWatch(t *testing.T) {
|
|||
)
|
||||
}
|
||||
|
||||
func TestConvertToTableList(t *testing.T) {
|
||||
storage, _, _, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
|
||||
columns := []metav1alpha1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
|
||||
{Name: "Ready", Type: "string", Description: "The aggregate readiness state of this pod for accepting traffic."},
|
||||
{Name: "Status", Type: "string", Description: "The aggregate status of the containers in this pod."},
|
||||
{Name: "Restarts", Type: "integer", Description: "The number of times the containers in this pod have been restarted."},
|
||||
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
|
||||
{Name: "IP", Type: "string", Priority: 1, Description: v1.PodStatus{}.SwaggerDoc()["podIP"]},
|
||||
{Name: "Node", Type: "string", Priority: 1, Description: v1.PodSpec{}.SwaggerDoc()["nodeName"]},
|
||||
}
|
||||
|
||||
pod1 := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo", CreationTimestamp: metav1.NewTime(time.Now().Add(-370 * 24 * time.Hour))},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1"},
|
||||
{Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 9376}}},
|
||||
},
|
||||
NodeName: "test-node",
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
PodIP: "10.1.2.3",
|
||||
Phase: api.PodPending,
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Name: "ctr1", State: api.ContainerState{Running: &api.ContainerStateRunning{}}, RestartCount: 10, Ready: true},
|
||||
{Name: "ctr2", State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, RestartCount: 0},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
in runtime.Object
|
||||
out *metav1alpha1.Table
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
in: nil,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
in: &api.Pod{},
|
||||
out: &metav1alpha1.Table{
|
||||
ColumnDefinitions: columns,
|
||||
Rows: []metav1alpha1.TableRow{
|
||||
{Cells: []interface{}{"", "0/0", "", 0, "<unknown>", "<none>", "<none>"}, Object: runtime.RawExtension{Object: &api.Pod{}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: pod1,
|
||||
out: &metav1alpha1.Table{
|
||||
ColumnDefinitions: columns,
|
||||
Rows: []metav1alpha1.TableRow{
|
||||
{Cells: []interface{}{"foo", "1/2", "Pending", 10, "1y", "10.1.2.3", "test-node"}, Object: runtime.RawExtension{Object: pod1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: &api.PodList{},
|
||||
out: &metav1alpha1.Table{ColumnDefinitions: columns},
|
||||
},
|
||||
}
|
||||
for i, test := range testCases {
|
||||
out, err := storage.ConvertToTable(ctx, test.in, nil)
|
||||
if err != nil {
|
||||
if test.err {
|
||||
continue
|
||||
}
|
||||
t.Errorf("%d: error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(test.out, out) {
|
||||
t.Errorf("%d: mismatch: %s", i, diff.ObjectReflectDiff(test.out, out))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdCreate(t *testing.T) {
|
||||
storage, bindingStorage, _, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
|
|
|
@ -11,6 +11,7 @@ load(
|
|||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"meta_test.go",
|
||||
"multirestmapper_test.go",
|
||||
"priority_test.go",
|
||||
"restmapper_test.go",
|
||||
|
@ -18,8 +19,12 @@ go_test(
|
|||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ func AsPartialObjectMetadata(m metav1.Object) *metav1alpha1.PartialObjectMetadat
|
|||
OwnerReferences: m.GetOwnerReferences(),
|
||||
Finalizers: m.GetFinalizers(),
|
||||
ClusterName: m.GetClusterName(),
|
||||
Initializers: m.GetInitializers(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
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 meta
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
|
||||
fuzz "github.com/google/gofuzz"
|
||||
)
|
||||
|
||||
func TestAsPartialObjectMetadata(t *testing.T) {
|
||||
f := fuzz.New().NilChance(.5).NumElements(0, 1).RandSource(rand.NewSource(1))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
m := &metav1.ObjectMeta{}
|
||||
f.Fuzz(m)
|
||||
partial := AsPartialObjectMetadata(m)
|
||||
if !reflect.DeepEqual(&partial.ObjectMeta, m) {
|
||||
t.Fatalf("incomplete partial object metadata: %s", diff.ObjectReflectDiff(&partial.ObjectMeta, m))
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
m := &metav1alpha1.PartialObjectMetadata{}
|
||||
f.Fuzz(&m.ObjectMeta)
|
||||
partial := AsPartialObjectMetadata(m)
|
||||
if !reflect.DeepEqual(&partial.ObjectMeta, &m.ObjectMeta) {
|
||||
t.Fatalf("incomplete partial object metadata: %s", diff.ObjectReflectDiff(&partial.ObjectMeta, &m.ObjectMeta))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -149,6 +149,9 @@ func (meta *ObjectMeta) GetFinalizers() []string { return m
|
|||
func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers }
|
||||
|
||||
func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference {
|
||||
if meta.OwnerReferences == nil {
|
||||
return nil
|
||||
}
|
||||
ret := make([]OwnerReference, len(meta.OwnerReferences))
|
||||
for i := 0; i < len(meta.OwnerReferences); i++ {
|
||||
ret[i].Kind = meta.OwnerReferences[i].Kind
|
||||
|
@ -168,6 +171,10 @@ func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference {
|
|||
}
|
||||
|
||||
func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
|
||||
if references == nil {
|
||||
meta.OwnerReferences = nil
|
||||
return
|
||||
}
|
||||
newReferences := make([]OwnerReference, len(references))
|
||||
for i := 0; i < len(references); i++ {
|
||||
newReferences[i].Kind = references[i].Kind
|
||||
|
|
|
@ -34,7 +34,7 @@ import (
|
|||
// transformResponseObject takes an object loaded from storage and performs any necessary transformations.
|
||||
// Will write the complete response object.
|
||||
func transformResponseObject(ctx request.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, result runtime.Object) {
|
||||
// TODO: use returned serializer
|
||||
// TODO: fetch the media type much earlier in request processing and pass it into this method.
|
||||
mediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
status := responsewriters.ErrorToAPIStatus(err)
|
||||
|
@ -169,7 +169,7 @@ func transformResponseObject(ctx request.Context, scope RequestScope, req *http.
|
|||
}
|
||||
}
|
||||
|
||||
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
responsewriters.WriteObject(ctx, statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
}
|
||||
|
||||
// errNotAcceptable indicates Accept negotiation has failed
|
||||
|
|
|
@ -62,6 +62,7 @@ go_library(
|
|||
"//vendor/k8s.io/apimachinery/pkg/api/validation/path:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -155,6 +156,9 @@ type Store struct {
|
|||
// ExportStrategy implements resource-specific behavior during export,
|
||||
// optional. Exported objects are not decorated.
|
||||
ExportStrategy rest.RESTExportStrategy
|
||||
// TableConvertor is an optional interface for transforming items or lists
|
||||
// of items into tabular output. If unset, the default will be used.
|
||||
TableConvertor rest.TableConvertor
|
||||
|
||||
// Storage is the interface for the underlying storage for the resource.
|
||||
Storage storage.Interface
|
||||
|
@ -169,6 +173,7 @@ type Store struct {
|
|||
// Note: the rest.StandardStorage interface aggregates the common REST verbs
|
||||
var _ rest.StandardStorage = &Store{}
|
||||
var _ rest.Exporter = &Store{}
|
||||
var _ rest.TableConvertor = &Store{}
|
||||
|
||||
const OptimisticLockErrorMsg = "the object has been modified; please apply your changes to the latest version and try again"
|
||||
|
||||
|
@ -1230,3 +1235,10 @@ func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Store) ConvertToTable(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error) {
|
||||
if e.TableConvertor != nil {
|
||||
return e.TableConvertor.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
return rest.DefaultTableConvertor.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ go_library(
|
|||
"export.go",
|
||||
"meta.go",
|
||||
"rest.go",
|
||||
"table.go",
|
||||
"update.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
|
@ -42,6 +43,7 @@ go_library(
|
|||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
|
||||
|
|
|
@ -118,7 +118,7 @@ type GetterWithOptions interface {
|
|||
}
|
||||
|
||||
type TableConvertor interface {
|
||||
ConvertToTableList(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.TableList, error)
|
||||
ConvertToTable(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error)
|
||||
}
|
||||
|
||||
// Deleter is an object that can delete a named RESTful resource.
|
||||
|
|
|
@ -32,15 +32,15 @@ type defaultTableConvertor struct{}
|
|||
|
||||
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
||||
|
||||
func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.TableList, error) {
|
||||
var table metav1alpha1.TableList
|
||||
func (defaultTableConvertor) ConvertToTable(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error) {
|
||||
var table metav1alpha1.Table
|
||||
fn := func(obj runtime.Object) error {
|
||||
m, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
// TODO: skip objects we don't recognize
|
||||
return nil
|
||||
}
|
||||
table.Items = append(table.Items, metav1alpha1.TableListItem{
|
||||
table.Rows = append(table.Rows, metav1alpha1.TableRow{
|
||||
Cells: []interface{}{m.GetClusterName(), m.GetNamespace(), m.GetName(), m.GetCreationTimestamp().Time.UTC().Format(time.RFC3339)},
|
||||
Object: runtime.RawExtension{Object: obj},
|
||||
})
|
||||
|
@ -56,7 +56,7 @@ func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, o
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
table.Headers = []metav1alpha1.TableListHeader{
|
||||
table.ColumnDefinitions = []metav1alpha1.TableColumnDefinition{
|
||||
{Name: "Cluster Name", Type: "string", Description: swaggerMetadataDescriptions["clusterName"]},
|
||||
{Name: "Namespace", Type: "string", Description: swaggerMetadataDescriptions["namespace"]},
|
||||
{Name: "Name", Type: "string", Description: swaggerMetadataDescriptions["name"]},
|
||||
|
@ -71,8 +71,8 @@ func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, o
|
|||
return &table, nil
|
||||
}
|
||||
|
||||
func trimColumn(column int, table *metav1alpha1.TableList) bool {
|
||||
for _, item := range table.Items {
|
||||
func trimColumn(column int, table *metav1alpha1.Table) bool {
|
||||
for _, item := range table.Rows {
|
||||
switch t := item.Cells[column].(type) {
|
||||
case string:
|
||||
if len(t) > 0 {
|
||||
|
@ -85,22 +85,22 @@ func trimColumn(column int, table *metav1alpha1.TableList) bool {
|
|||
}
|
||||
}
|
||||
if column == 0 {
|
||||
table.Headers = table.Headers[1:]
|
||||
table.ColumnDefinitions = table.ColumnDefinitions[1:]
|
||||
} else {
|
||||
for j := column; j < len(table.Headers); j++ {
|
||||
table.Headers[j] = table.Headers[j+1]
|
||||
for j := column; j < len(table.ColumnDefinitions); j++ {
|
||||
table.ColumnDefinitions[j] = table.ColumnDefinitions[j+1]
|
||||
}
|
||||
}
|
||||
for i := range table.Items {
|
||||
cells := table.Items[i].Cells
|
||||
for i := range table.Rows {
|
||||
cells := table.Rows[i].Cells
|
||||
if column == 0 {
|
||||
table.Items[i].Cells = cells[1:]
|
||||
table.Rows[i].Cells = cells[1:]
|
||||
continue
|
||||
}
|
||||
for j := column; j < len(cells); j++ {
|
||||
cells[j] = cells[j+1]
|
||||
}
|
||||
table.Items[i].Cells = cells[:len(cells)-1]
|
||||
table.Rows[i].Cells = cells[:len(cells)-1]
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -330,6 +330,10 @@
|
|||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
|
Loading…
Reference in New Issue