Refactor printers to support rendering as a Table

Return tables from the server.
This commit is contained in:
Clayton Coleman 2017-05-26 19:00:01 -04:00
parent f203e42cb9
commit 7ce63eb608
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
22 changed files with 951 additions and 161 deletions

View File

@ -192,7 +192,7 @@ func Example_printReplicationControllerWithNamespace() {
}, },
} }
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout) err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }
@ -247,7 +247,7 @@ func Example_printMultiContainersReplicationControllerWithWide() {
}, },
} }
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout) err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }
@ -301,7 +301,7 @@ func Example_printReplicationController() {
}, },
} }
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout) err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }
@ -344,7 +344,7 @@ func Example_printPodWithWideFormat() {
}, },
} }
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, pod, os.Stdout) err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }
@ -390,7 +390,7 @@ func Example_printPodWithShowLabels() {
}, },
} }
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, pod, os.Stdout) err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }
@ -514,7 +514,7 @@ func Example_printPodHideTerminated() {
} }
for _, pod := range filteredPodList { for _, pod := range filteredPodList {
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, pod, os.Stdout) err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }
@ -542,7 +542,7 @@ func Example_printPodShowAll() {
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr) cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList() podList := newAllPhasePodList()
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, podList, os.Stdout) err := f.PrintObject(cmd, mapper, podList, printers.GetNewTabWriter(os.Stdout))
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }
@ -616,9 +616,10 @@ func Example_printServiceWithNamespacesAndLabels() {
} }
ld := strings.NewLineDelimiter(os.Stdout, "|") ld := strings.NewLineDelimiter(os.Stdout, "|")
defer ld.Flush() defer ld.Flush()
out := printers.GetNewTabWriter(ld)
defer out.Flush()
mapper, _ := f.Object() mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, svc, ld) err := f.PrintObject(cmd, mapper, svc, out)
if err != nil { if err != nil {
fmt.Printf("Unexpected error: %v", err) fmt.Printf("Unexpected error: %v", err)
} }

View File

@ -28,12 +28,14 @@ go_library(
"//pkg/util/slice:go_default_library", "//pkg/util/slice:go_default_library",
"//vendor/github.com/fatih/camelcase:go_default_library", "//vendor/github.com/fatih/camelcase:go_default_library",
"//vendor/github.com/ghodss/yaml: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/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/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime: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/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors: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", "//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
], ],
) )
@ -63,6 +65,7 @@ filegroup(
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//pkg/printers/internalversion:all-srcs", "//pkg/printers/internalversion:all-srcs",
"//pkg/printers/storage:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],
) )

View File

@ -26,20 +26,31 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/fatih/camelcase" "github.com/fatih/camelcase"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/meta" "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/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/slice" "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. var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
type handlerEntry struct { type handlerEntry struct {
columns []string columnDefinitions []metav1alpha1.TableColumnDefinition
columnsWithWide []string printRows bool
printFunc reflect.Value printFunc reflect.Value
args []reflect.Value args []reflect.Value
} }
@ -57,6 +68,8 @@ type HumanReadablePrinter struct {
decoder runtime.Decoder decoder runtime.Decoder
} }
var _ PrintHandler = &HumanReadablePrinter{}
// NewHumanReadablePrinter creates a HumanReadablePrinter. // NewHumanReadablePrinter creates a HumanReadablePrinter.
// If encoder and decoder are provided, an attempt to convert unstructured types to internal types is made. // 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 { 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 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 // GetResourceKind returns the type currently set for a resource
func (h *HumanReadablePrinter) GetResourceKind() string { func (h *HumanReadablePrinter) GetResourceKind() string {
return h.options.Kind 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. // 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 { 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) printFuncValue := reflect.ValueOf(printFunc)
if err := h.validatePrintHandlerFunc(printFuncValue); err != nil { if err := ValidatePrintHandlerFunc(printFuncValue); err != nil {
glog.Errorf("Unable to add print handler: %v", err) utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
return err return err
} }
objType := printFuncValue.Type().In(0) entry := &handlerEntry{
h.handlerMap[objType] = &handlerEntry{ columnDefinitions: columnDefinitions,
columns: columns,
columnsWithWide: columnsWithWide,
printFunc: printFuncValue, 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
}
// 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 return nil
} }
// validatePrintHandlerFunc validates print handler signature. // ValidatePrintHandlerFunc validates print handler signature.
// printFunc is the function that will be called to print an object. // printFunc is the function that will be called to print an object.
// It must be of the following type: // It must be of the following type:
// func printFunc(object ObjectType, w io.Writer, options PrintOptions) error // func printFunc(object ObjectType, w io.Writer, options PrintOptions) error
// where ObjectType is the type of the object that will be printed. // 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 { 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)
} }
@ -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. // 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 { 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 // 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 w, found := output.(*tabwriter.Writer); found {
if !found {
w = GetNewTabWriter(output)
defer w.Flush() 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 // 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. // trying to print, since the printers are keyed by type. This is extremely expensive.
if h.encoder != nil && h.decoder != nil { 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) t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil { if handler := h.handlerMap[t]; handler != nil {
if !h.options.NoHeaders && t != h.lastType { if !h.options.NoHeaders && t != h.lastType {
headers := handler.columns var headers []string
if h.options.Wide { for _, column := range handler.columnDefinitions {
headers = append(headers, handler.columnsWithWide...) if column.Priority != 0 && !h.options.Wide {
continue
}
headers = append(headers, strings.ToUpper(column.Name))
} }
headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...) headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...)
// LABELS is always the last column. // 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 { if h.options.WithNamespace {
headers = append(withNamespacePrefixColumns, headers...) headers = append(withNamespacePrefixColumns, headers...)
} }
h.printHeader(headers, w) h.printHeader(headers, output)
h.lastType = t 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] resultValue := handler.printFunc.Call(args)[0]
if resultValue.IsNil() { if resultValue.IsNil() {
return nil 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. // we don't recognize this type, but we can still attempt to print some reasonable information about.
unstructured, ok := obj.(runtime.Unstructured) unstructured, ok := obj.(runtime.Unstructured)
if !ok { 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() content := unstructured.UnstructuredContent()
@ -255,12 +410,12 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
if h.options.WithNamespace { if h.options.WithNamespace {
headers = append(withNamespacePrefixColumns, headers...) headers = append(withNamespacePrefixColumns, headers...)
} }
h.printHeader(headers, w) h.printHeader(headers, output)
h.lastType = t h.lastType = t
} }
// if the error isn't nil, report the "I don't recognize this" error // 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 err
} }
return nil 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) 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 // 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 { func printUnstructured(unstructured runtime.Unstructured, w io.Writer, additionalFields []string, options PrintOptions) error {
metadata, err := meta.Accessor(unstructured) metadata, err := meta.Accessor(unstructured)
@ -349,6 +748,30 @@ func formatShowLabelsHeader(showLabels bool, t reflect.Type) []string {
return nil 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 // FormatResourceName receives a resource kind, name, and boolean specifying
// whether or not to update the current name to "kind/name" // whether or not to update the current name to "kind/name"
func FormatResourceName(kind, name string, withKind bool) string { 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 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
}

View File

@ -40,6 +40,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/api/resource: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/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured: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:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/yaml: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/helper/qos:go_default_library",
"//pkg/api/ref:go_default_library", "//pkg/api/ref:go_default_library",
"//pkg/api/resource:go_default_library", "//pkg/api/resource:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/apps:go_default_library", "//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library", "//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch: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/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource: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/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/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels: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/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",

View File

@ -27,12 +27,15 @@ import (
"time" "time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/federation/apis/federation" "k8s.io/kubernetes/federation/apis/federation"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/events" "k8s.io/kubernetes/pkg/api/events"
"k8s.io/kubernetes/pkg/api/helper" "k8s.io/kubernetes/pkg/api/helper"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch" "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 // NOTE: When adding a new resource type here, please update the list
// pkg/kubectl/cmd/get.go to reflect the new resource type. // pkg/kubectl/cmd/get.go to reflect the new resource type.
var ( var (
podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}
podWideColumns = []string{"IP", "NODE"}
podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"} podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"}
podDisruptionBudgetColumns = []string{"NAME", "MIN-AVAILABLE", "MAX-UNAVAILABLE", "ALLOWED-DISRUPTIONS", "AGE"} podDisruptionBudgetColumns = []string{"NAME", "MIN-AVAILABLE", "MAX-UNAVAILABLE", "ALLOWED-DISRUPTIONS", "AGE"}
replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"} replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"}
@ -105,27 +106,21 @@ var (
podPresetColumns = []string{"NAME", "AGE"} 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. // AddHandlers adds print handlers for default Kubernetes types dealing with internal versions.
func AddHandlers(h *printers.HumanReadablePrinter) { // TODO: handle errors from Handler
h.Handler(podColumns, podWideColumns, printPodList) func AddHandlers(h printers.PrintHandler) {
h.Handler(podColumns, podWideColumns, printPod) 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, printPodTemplate)
h.Handler(podTemplateColumns, nil, printPodTemplateList) h.Handler(podTemplateColumns, nil, printPodTemplateList)
h.Handler(podDisruptionBudgetColumns, nil, printPodDisruptionBudget) h.Handler(podDisruptionBudgetColumns, nil, printPodDisruptionBudget)
@ -247,10 +242,24 @@ func translateTimestamp(timestamp metav1.Time) string {
return printers.ShortHumanDuration(time.Now().Sub(timestamp.Time)) return printers.ShortHumanDuration(time.Now().Sub(timestamp.Time))
} }
func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) error { var (
name := printers.FormatResourceName(options.Kind, pod.Name, options.WithKind) podSuccessConditions = []metav1alpha1.TableRowCondition{{Type: metav1alpha1.RowCompleted, Status: metav1alpha1.ConditionTrue, Reason: string(api.PodSucceeded), Message: "The pod has completed successfully."}}
namespace := pod.Namespace 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 restarts := 0
totalContainers := len(pod.Spec.Containers) totalContainers := len(pod.Spec.Containers)
readyContainers := 0 readyContainers := 0
@ -260,6 +269,17 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
reason = pod.Status.Reason 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 initializing := false
for i := range pod.Status.InitContainerStatuses { for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i] container := pod.Status.InitContainerStatuses[i]
@ -316,21 +336,7 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
reason = "Terminating" reason = "Terminating"
} }
if options.WithNamespace { row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, restarts, translateTimestamp(pod.CreationTimestamp))
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
}
if options.Wide { if options.Wide {
nodeName := pod.Spec.NodeName nodeName := pod.Spec.NodeName
@ -341,22 +347,10 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
if nodeName == "" { if nodeName == "" {
nodeName = "<none>" nodeName = "<none>"
} }
if _, err := fmt.Fprintf(w, "\t%s\t%s", row.Cells = append(row.Cells, podIP, nodeName)
podIP,
nodeName,
); err != nil {
return err
}
} }
if _, err := fmt.Fprint(w, printers.AppendLabels(pod.Labels, options.ColumnLabels)); err != nil { return []metav1alpha1.TableRow{row}, nil
return err
}
if _, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, pod.Labels)); err != nil {
return err
}
return nil
} }
func printPodTemplate(pod *api.PodTemplate, w io.Writer, options printers.PrintOptions) error { func printPodTemplate(pod *api.PodTemplate, w io.Writer, options printers.PrintOptions) error {

View File

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -31,6 +32,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml" yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
@ -267,7 +269,7 @@ func TestCustomTypePrinting(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("An error occurred printing the custom type: %#v", err) t.Fatalf("An error occurred printing the custom type: %#v", err)
} }
expectedOutput := "Data\ntest object" expectedOutput := "DATA\ntest object"
if buffer.String() != expectedOutput { if buffer.String() != expectedOutput {
t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String()) 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 { if err != nil {
t.Fatalf("An error occurred printing the custom type: %#v", err) 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 { if buffer.String() != expectedOutput {
t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String()) 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 { if test.isNamespaced {
// Expect output to include namespace when requested. // Expect output to include namespace when requested.
printer := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{ 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)) matched := contains(strings.Fields(buffer.String()), fmt.Sprintf("%s", namespaceName))
if !matched { 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 { } else {
// Expect error when trying to get all namespaces for un-namespaced object. // 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) { func TestPrintPod(t *testing.T) {
tests := []struct { tests := []struct {
pod api.Pod pod api.Pod
expect string expect []metav1alpha1.TableRow
}{ }{
{ {
// Test name, num of containers, restarts, container ready status // 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 // 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 // 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 // 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 // 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 i, test := range tests {
for _, test := range tests { rows, err := printPod(&test.pod, printers.PrintOptions{ShowAll: true})
printPod(&test.pod, buf, printers.PrintOptions{ShowAll: true}) if err != nil {
// We ignore time t.Fatal(err)
if !strings.HasPrefix(buf.String(), test.expect) { }
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String()) 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) { func TestPrintNonTerminatedPod(t *testing.T) {
tests := []struct { tests := []struct {
pod api.Pod pod api.Pod
expect string expect []metav1alpha1.TableRow
}{ }{
{ {
// Test pod phase Running should be printed // 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 // 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 // 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 // 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 // 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 i, test := range tests {
for _, test := range tests { table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.pod, printers.PrintOptions{})
printPod(&test.pod, buf, printers.PrintOptions{}) if err != nil {
// We ignore time t.Fatal(err)
if !strings.HasPrefix(buf.String(), test.expect) { }
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String()) 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 { tests := []struct {
pod api.Pod pod api.Pod
labelColumns []string labelColumns []string
startsWith string expect []metav1alpha1.TableRow
endsWith string
}{ }{
{ {
// Test name, num of containers, restarts, container ready status // Test name, num of containers, restarts, container ready status
@ -1493,8 +1587,7 @@ func TestPrintPodWithLabels(t *testing.T) {
}, },
}, },
[]string{"col1", "COL2"}, []string{"col1", "COL2"},
"test1\t1/2\tpodPhase\t6\t", []metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>", "asd", "zxc"}}},
"\tasd\tzxc\n",
}, },
{ {
// Test name, num of containers, restarts, container ready status // Test name, num of containers, restarts, container ready status
@ -1513,19 +1606,22 @@ func TestPrintPodWithLabels(t *testing.T) {
}, },
}, },
[]string{}, []string{},
"test1\t1/2\tpodPhase\t6\t", []metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>"}}},
"\n",
}, },
} }
buf := bytes.NewBuffer([]byte{}) for i, test := range tests {
for _, test := range tests { table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.pod, printers.PrintOptions{ColumnLabels: test.labelColumns})
printPod(&test.pod, buf, printers.PrintOptions{ColumnLabels: test.labelColumns}) if err != nil {
// We ignore time t.Fatal(err)
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()) 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) { func TestPrintPodShowLabels(t *testing.T) {
tests := []struct { tests := []struct {
pod api.Pod pod api.Pod
startsWith string
endsWith string
showLabels bool showLabels bool
expect []metav1alpha1.TableRow
}{ }{
{ {
// Test name, num of containers, restarts, container ready status // 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, true,
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>", "COL2=zxc,col1=asd"}}},
}, },
{ {
// Test name, num of containers, restarts, container ready status // 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, false,
[]metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "<unknown>"}}},
}, },
} }
buf := bytes.NewBuffer([]byte{}) for i, test := range tests {
for _, test := range tests { table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.pod, printers.PrintOptions{ShowLabels: test.showLabels})
printPod(&test.pod, buf, printers.PrintOptions{ShowLabels: test.showLabels}) if err != nil {
// We ignore time t.Fatal(err)
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()) 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()
} }
} }

View File

@ -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"],
)

View File

@ -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})
}

View File

@ -15,12 +15,14 @@ go_test(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/registry/registrytest:go_default_library", "//pkg/registry/registrytest:go_default_library",
"//pkg/securitycontext:go_default_library", "//pkg/securitycontext:go_default_library",
"//vendor/golang.org/x/net/context: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/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors: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/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/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels: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:go_default_library",
@ -49,6 +51,9 @@ go_library(
"//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:go_default_library",
"//pkg/client/retry:go_default_library", "//pkg/client/retry:go_default_library",
"//pkg/kubelet/client: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/cachesize:go_default_library",
"//pkg/registry/core/pod:go_default_library", "//pkg/registry/core/pod:go_default_library",
"//pkg/registry/core/pod/rest:go_default_library", "//pkg/registry/core/pod/rest:go_default_library",

View File

@ -35,6 +35,9 @@ import (
"k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/api/validation"
policyclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion" policyclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion"
"k8s.io/kubernetes/pkg/kubelet/client" "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/cachesize"
"k8s.io/kubernetes/pkg/registry/core/pod" "k8s.io/kubernetes/pkg/registry/core/pod"
podrest "k8s.io/kubernetes/pkg/registry/core/pod/rest" 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. // 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 { func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) PodStorage {
store := &genericregistry.Store{ store := &genericregistry.Store{
Copier: api.Scheme, Copier: api.Scheme,
NewFunc: func() runtime.Object { return &api.Pod{} }, NewFunc: func() runtime.Object { return &api.Pod{} },
@ -73,6 +77,8 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGet
UpdateStrategy: pod.Strategy, UpdateStrategy: pod.Strategy,
DeleteStrategy: pod.Strategy, DeleteStrategy: pod.Strategy,
ReturnDeletedObject: true, ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TablePrinter: printers.NewTablePrinter().With(printersinternal.AddHandlers)},
} }
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: pod.GetAttrs, TriggerFunc: pod.NodeNameTriggerFunc} options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: pod.GetAttrs, TriggerFunc: pod.NodeNameTriggerFunc}
if err := store.CompleteWithOptions(options); err != nil { if err := store.CompleteWithOptions(options); err != nil {

View File

@ -19,11 +19,13 @@ package storage
import ( import (
"strings" "strings"
"testing" "testing"
"time"
"golang.org/x/net/context" "golang.org/x/net/context"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -35,6 +37,7 @@ import (
storeerr "k8s.io/apiserver/pkg/storage/errors" storeerr "k8s.io/apiserver/pkg/storage/errors"
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/registry/registrytest" "k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/securitycontext" "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) { func TestEtcdCreate(t *testing.T) {
storage, bindingStorage, _, server := newStorage(t) storage, bindingStorage, _, server := newStorage(t)
defer server.Terminate(t) defer server.Terminate(t)

View File

@ -11,6 +11,7 @@ load(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"meta_test.go",
"multirestmapper_test.go", "multirestmapper_test.go",
"priority_test.go", "priority_test.go",
"restmapper_test.go", "restmapper_test.go",
@ -18,8 +19,12 @@ go_test(
library = ":go_default_library", library = ":go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
deps = [ 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:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
], ],
) )

View File

@ -114,6 +114,7 @@ func AsPartialObjectMetadata(m metav1.Object) *metav1alpha1.PartialObjectMetadat
OwnerReferences: m.GetOwnerReferences(), OwnerReferences: m.GetOwnerReferences(),
Finalizers: m.GetFinalizers(), Finalizers: m.GetFinalizers(),
ClusterName: m.GetClusterName(), ClusterName: m.GetClusterName(),
Initializers: m.GetInitializers(),
}, },
} }
} }

View File

@ -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))
}
}
}

View File

@ -149,6 +149,9 @@ func (meta *ObjectMeta) GetFinalizers() []string { return m
func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers } func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers }
func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference {
if meta.OwnerReferences == nil {
return nil
}
ret := make([]OwnerReference, len(meta.OwnerReferences)) ret := make([]OwnerReference, len(meta.OwnerReferences))
for i := 0; i < len(meta.OwnerReferences); i++ { for i := 0; i < len(meta.OwnerReferences); i++ {
ret[i].Kind = meta.OwnerReferences[i].Kind ret[i].Kind = meta.OwnerReferences[i].Kind
@ -168,6 +171,10 @@ func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference {
} }
func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
if references == nil {
meta.OwnerReferences = nil
return
}
newReferences := make([]OwnerReference, len(references)) newReferences := make([]OwnerReference, len(references))
for i := 0; i < len(references); i++ { for i := 0; i < len(references); i++ {
newReferences[i].Kind = references[i].Kind newReferences[i].Kind = references[i].Kind

View File

@ -34,7 +34,7 @@ import (
// transformResponseObject takes an object loaded from storage and performs any necessary transformations. // transformResponseObject takes an object loaded from storage and performs any necessary transformations.
// Will write the complete response object. // Will write the complete response object.
func transformResponseObject(ctx request.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, result runtime.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) mediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
if err != nil { if err != nil {
status := responsewriters.ErrorToAPIStatus(err) 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 // errNotAcceptable indicates Accept negotiation has failed

View File

@ -62,6 +62,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/api/validation/path:go_default_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/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1: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/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels: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:go_default_library",

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/api/validation/path"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -155,6 +156,9 @@ type Store struct {
// ExportStrategy implements resource-specific behavior during export, // ExportStrategy implements resource-specific behavior during export,
// optional. Exported objects are not decorated. // optional. Exported objects are not decorated.
ExportStrategy rest.RESTExportStrategy 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 is the interface for the underlying storage for the resource.
Storage storage.Interface Storage storage.Interface
@ -169,6 +173,7 @@ type Store struct {
// Note: the rest.StandardStorage interface aggregates the common REST verbs // Note: the rest.StandardStorage interface aggregates the common REST verbs
var _ rest.StandardStorage = &Store{} var _ rest.StandardStorage = &Store{}
var _ rest.Exporter = &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" 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 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)
}

View File

@ -31,6 +31,7 @@ go_library(
"export.go", "export.go",
"meta.go", "meta.go",
"rest.go", "rest.go",
"table.go",
"update.go", "update.go",
], ],
tags = ["automanaged"], 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/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1: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/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:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",

View File

@ -118,7 +118,7 @@ type GetterWithOptions interface {
} }
type TableConvertor 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. // Deleter is an object that can delete a named RESTful resource.

View File

@ -32,15 +32,15 @@ type defaultTableConvertor struct{}
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.TableList, error) { func (defaultTableConvertor) ConvertToTable(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error) {
var table metav1alpha1.TableList var table metav1alpha1.Table
fn := func(obj runtime.Object) error { fn := func(obj runtime.Object) error {
m, err := meta.Accessor(obj) m, err := meta.Accessor(obj)
if err != nil { if err != nil {
// TODO: skip objects we don't recognize // TODO: skip objects we don't recognize
return nil 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)}, Cells: []interface{}{m.GetClusterName(), m.GetNamespace(), m.GetName(), m.GetCreationTimestamp().Time.UTC().Format(time.RFC3339)},
Object: runtime.RawExtension{Object: obj}, Object: runtime.RawExtension{Object: obj},
}) })
@ -56,7 +56,7 @@ func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, o
return nil, err return nil, err
} }
} }
table.Headers = []metav1alpha1.TableListHeader{ table.ColumnDefinitions = []metav1alpha1.TableColumnDefinition{
{Name: "Cluster Name", Type: "string", Description: swaggerMetadataDescriptions["clusterName"]}, {Name: "Cluster Name", Type: "string", Description: swaggerMetadataDescriptions["clusterName"]},
{Name: "Namespace", Type: "string", Description: swaggerMetadataDescriptions["namespace"]}, {Name: "Namespace", Type: "string", Description: swaggerMetadataDescriptions["namespace"]},
{Name: "Name", Type: "string", Description: swaggerMetadataDescriptions["name"]}, {Name: "Name", Type: "string", Description: swaggerMetadataDescriptions["name"]},
@ -71,8 +71,8 @@ func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, o
return &table, nil return &table, nil
} }
func trimColumn(column int, table *metav1alpha1.TableList) bool { func trimColumn(column int, table *metav1alpha1.Table) bool {
for _, item := range table.Items { for _, item := range table.Rows {
switch t := item.Cells[column].(type) { switch t := item.Cells[column].(type) {
case string: case string:
if len(t) > 0 { if len(t) > 0 {
@ -85,22 +85,22 @@ func trimColumn(column int, table *metav1alpha1.TableList) bool {
} }
} }
if column == 0 { if column == 0 {
table.Headers = table.Headers[1:] table.ColumnDefinitions = table.ColumnDefinitions[1:]
} else { } else {
for j := column; j < len(table.Headers); j++ { for j := column; j < len(table.ColumnDefinitions); j++ {
table.Headers[j] = table.Headers[j+1] table.ColumnDefinitions[j] = table.ColumnDefinitions[j+1]
} }
} }
for i := range table.Items { for i := range table.Rows {
cells := table.Items[i].Cells cells := table.Rows[i].Cells
if column == 0 { if column == 0 {
table.Items[i].Cells = cells[1:] table.Rows[i].Cells = cells[1:]
continue continue
} }
for j := column; j < len(cells); j++ { for j := column; j < len(cells); j++ {
cells[j] = cells[j+1] cells[j] = cells[j+1]
} }
table.Items[i].Cells = cells[:len(cells)-1] table.Rows[i].Cells = cells[:len(cells)-1]
} }
return true return true
} }

View File

@ -330,6 +330,10 @@
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion", "ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"