Fresh dep ensure

This commit is contained in:
Mike Cronce
2018-11-26 13:23:56 -05:00
parent 93cb8a04d7
commit 407478ab9a
9016 changed files with 551394 additions and 279685 deletions

View File

@ -9,27 +9,20 @@ load(
go_library(
name = "go_default_library",
srcs = [
"customcolumn.go",
"customcolumn_flags.go",
"humanreadable.go",
"interface.go",
"tabwriter.go",
],
importpath = "k8s.io/kubernetes/pkg/printers",
deps = [
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//vendor/github.com/spf13/cobra: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/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1: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/runtime:go_default_library",
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
],
)
@ -52,19 +45,12 @@ filegroup(
go_test(
name = "go_default_test",
srcs = [
"customcolumn_flags_test.go",
"customcolumn_test.go",
"humanreadable_test.go",
],
srcs = ["humanreadable_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)

View File

@ -1,242 +0,0 @@
/*
Copyright 2014 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 printers
import (
"bufio"
"bytes"
"fmt"
"io"
"reflect"
"regexp"
"strings"
"text/tabwriter"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/jsonpath"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
)
var jsonRegexp = regexp.MustCompile("^\\{\\.?([^{}]+)\\}$|^\\.?([^{}]+)$")
// RelaxedJSONPathExpression attempts to be flexible with JSONPath expressions, it accepts:
// * metadata.name (no leading '.' or curly brances '{...}'
// * {metadata.name} (no leading '.')
// * .metadata.name (no curly braces '{...}')
// * {.metadata.name} (complete expression)
// And transforms them all into a valid jsonpath expression:
// {.metadata.name}
func RelaxedJSONPathExpression(pathExpression string) (string, error) {
if len(pathExpression) == 0 {
return pathExpression, nil
}
submatches := jsonRegexp.FindStringSubmatch(pathExpression)
if submatches == nil {
return "", fmt.Errorf("unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or '{.name1.name2}'")
}
if len(submatches) != 3 {
return "", fmt.Errorf("unexpected submatch list: %v", submatches)
}
var fieldSpec string
if len(submatches[1]) != 0 {
fieldSpec = submatches[1]
} else {
fieldSpec = submatches[2]
}
return fmt.Sprintf("{.%s}", fieldSpec), nil
}
// NewCustomColumnsPrinterFromSpec creates a custom columns printer from a comma separated list of <header>:<jsonpath-field-spec> pairs.
// e.g. NAME:metadata.name,API_VERSION:apiVersion creates a printer that prints:
//
// NAME API_VERSION
// foo bar
func NewCustomColumnsPrinterFromSpec(spec string, decoder runtime.Decoder, noHeaders bool) (*CustomColumnsPrinter, error) {
if len(spec) == 0 {
return nil, fmt.Errorf("custom-columns format specified but no custom columns given")
}
parts := strings.Split(spec, ",")
columns := make([]Column, len(parts))
for ix := range parts {
colSpec := strings.Split(parts[ix], ":")
if len(colSpec) != 2 {
return nil, fmt.Errorf("unexpected custom-columns spec: %s, expected <header>:<json-path-expr>", parts[ix])
}
spec, err := RelaxedJSONPathExpression(colSpec[1])
if err != nil {
return nil, err
}
columns[ix] = Column{Header: colSpec[0], FieldSpec: spec}
}
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: noHeaders}, nil
}
func splitOnWhitespace(line string) []string {
lineScanner := bufio.NewScanner(bytes.NewBufferString(line))
lineScanner.Split(bufio.ScanWords)
result := []string{}
for lineScanner.Scan() {
result = append(result, lineScanner.Text())
}
return result
}
// NewCustomColumnsPrinterFromTemplate creates a custom columns printer from a template stream. The template is expected
// to consist of two lines, whitespace separated. The first line is the header line, the second line is the jsonpath field spec
// For example, the template below:
// NAME API_VERSION
// {metadata.name} {apiVersion}
func NewCustomColumnsPrinterFromTemplate(templateReader io.Reader, decoder runtime.Decoder) (*CustomColumnsPrinter, error) {
scanner := bufio.NewScanner(templateReader)
if !scanner.Scan() {
return nil, fmt.Errorf("invalid template, missing header line. Expected format is one line of space separated headers, one line of space separated column specs.")
}
headers := splitOnWhitespace(scanner.Text())
if !scanner.Scan() {
return nil, fmt.Errorf("invalid template, missing spec line. Expected format is one line of space separated headers, one line of space separated column specs.")
}
specs := splitOnWhitespace(scanner.Text())
if len(headers) != len(specs) {
return nil, fmt.Errorf("number of headers (%d) and field specifications (%d) don't match", len(headers), len(specs))
}
columns := make([]Column, len(headers))
for ix := range headers {
spec, err := RelaxedJSONPathExpression(specs[ix])
if err != nil {
return nil, err
}
columns[ix] = Column{
Header: headers[ix],
FieldSpec: spec,
}
}
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: false}, nil
}
// Column represents a user specified column
type Column struct {
// The header to print above the column, general style is ALL_CAPS
Header string
// The pointer to the field in the object to print in JSONPath form
// e.g. {.ObjectMeta.Name}, see pkg/util/jsonpath for more details.
FieldSpec string
}
// CustomColumnPrinter is a printer that knows how to print arbitrary columns
// of data from templates specified in the `Columns` array
type CustomColumnsPrinter struct {
Columns []Column
Decoder runtime.Decoder
NoHeaders bool
// lastType records type of resource printed last so that we don't repeat
// header while printing same type of resources.
lastType reflect.Type
}
func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
// we need an actual value in order to retrieve the package path for an object.
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
return fmt.Errorf(printers.InternalObjectPrinterErr)
}
if w, found := out.(*tabwriter.Writer); !found {
w = GetNewTabWriter(out)
out = w
defer w.Flush()
}
t := reflect.TypeOf(obj)
if !s.NoHeaders && t != s.lastType {
headers := make([]string, len(s.Columns))
for ix := range s.Columns {
headers[ix] = s.Columns[ix].Header
}
fmt.Fprintln(out, strings.Join(headers, "\t"))
s.lastType = t
}
parsers := make([]*jsonpath.JSONPath, len(s.Columns))
for ix := range s.Columns {
parsers[ix] = jsonpath.New(fmt.Sprintf("column%d", ix)).AllowMissingKeys(true)
if err := parsers[ix].Parse(s.Columns[ix].FieldSpec); err != nil {
return err
}
}
if meta.IsListType(obj) {
objs, err := meta.ExtractList(obj)
if err != nil {
return err
}
for ix := range objs {
if err := s.printOneObject(objs[ix], parsers, out); err != nil {
return err
}
}
} else {
if err := s.printOneObject(obj, parsers, out); err != nil {
return err
}
}
return nil
}
func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jsonpath.JSONPath, out io.Writer) error {
columns := make([]string, len(parsers))
switch u := obj.(type) {
case *runtime.Unknown:
if len(u.Raw) > 0 {
var err error
if obj, err = runtime.Decode(s.Decoder, u.Raw); err != nil {
return fmt.Errorf("can't decode object for printing: %v (%s)", err, u.Raw)
}
}
}
for ix := range parsers {
parser := parsers[ix]
var values [][]reflect.Value
var err error
if unstructured, ok := obj.(runtime.Unstructured); ok {
values, err = parser.FindResults(unstructured.UnstructuredContent())
} else {
values, err = parser.FindResults(reflect.ValueOf(obj).Elem().Interface())
}
if err != nil {
return err
}
valueStrings := []string{}
if len(values) == 0 || len(values[0]) == 0 {
valueStrings = append(valueStrings, "<none>")
}
for arrIx := range values {
for valIx := range values[arrIx] {
valueStrings = append(valueStrings, fmt.Sprintf("%v", values[arrIx][valIx].Interface()))
}
}
columns[ix] = strings.Join(valueStrings, ",")
}
fmt.Fprintln(out, strings.Join(columns, "\t"))
return nil
}

View File

@ -1,109 +0,0 @@
/*
Copyright 2018 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 printers
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
var columnsFormats = map[string]bool{
"custom-columns-file": true,
"custom-columns": true,
}
// CustomColumnsPrintFlags provides default flags necessary for printing
// custom resource columns from an inline-template or file.
type CustomColumnsPrintFlags struct {
NoHeaders bool
TemplateArgument string
}
func (f *CustomColumnsPrintFlags) AllowedFormats() []string {
formats := make([]string, 0, len(columnsFormats))
for format := range columnsFormats {
formats = append(formats, format)
}
return formats
}
// ToPrinter receives an templateFormat and returns a printer capable of
// handling custom-column printing.
// Returns false if the specified templateFormat does not match a supported format.
// Supported format types can be found in pkg/printers/printers.go
func (f *CustomColumnsPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) {
if len(templateFormat) == 0 {
return nil, genericclioptions.NoCompatiblePrinterError{}
}
templateValue := ""
if len(f.TemplateArgument) == 0 {
for format := range columnsFormats {
format = format + "="
if strings.HasPrefix(templateFormat, format) {
templateValue = templateFormat[len(format):]
templateFormat = format[:len(format)-1]
break
}
}
} else {
templateValue = f.TemplateArgument
}
if _, supportedFormat := columnsFormats[templateFormat]; !supportedFormat {
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
}
if len(templateValue) == 0 {
return nil, fmt.Errorf("custom-columns format specified but no custom columns given")
}
decoder := scheme.Codecs.UniversalDecoder()
if templateFormat == "custom-columns-file" {
file, err := os.Open(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading template %s, %v\n", templateValue, err)
}
defer file.Close()
p, err := NewCustomColumnsPrinterFromTemplate(file, decoder)
return p, err
}
return NewCustomColumnsPrinterFromSpec(templateValue, decoder, f.NoHeaders)
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to custom-columns printing
func (f *CustomColumnsPrintFlags) AddFlags(c *cobra.Command) {}
// NewCustomColumnsPrintFlags returns flags associated with
// custom-column printing, with default values set.
// NoHeaders and TemplateArgument should be set by callers.
func NewCustomColumnsPrintFlags() *CustomColumnsPrintFlags {
return &CustomColumnsPrintFlags{
NoHeaders: false,
TemplateArgument: "",
}
}

View File

@ -1,139 +0,0 @@
/*
Copyright 2018 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 printers
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestPrinterSupportsExpectedCustomColumnFormats(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
customColumnsFile, err := ioutil.TempFile("", "printers_jsonpath_flags")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer func(tempFile *os.File) {
tempFile.Close()
os.Remove(tempFile.Name())
}(customColumnsFile)
fmt.Fprintf(customColumnsFile, "NAME\n.metadata.name")
testCases := []struct {
name string
outputFormat string
templateArg string
expectedError string
expectedParseError string
expectedOutput string
expectNoMatch bool
}{
{
name: "valid output format also containing the custom-columns argument succeeds",
outputFormat: "custom-columns=NAME:.metadata.name",
expectedOutput: "foo",
},
{
name: "valid output format and no --template argument results in an error",
outputFormat: "custom-columns",
expectedError: "custom-columns format specified but no custom columns given",
},
{
name: "valid output format and --template argument succeeds",
outputFormat: "custom-columns",
templateArg: "NAME:.metadata.name",
expectedOutput: "foo",
},
{
name: "custom-columns template file should match, and successfully return correct value",
outputFormat: "custom-columns-file",
templateArg: customColumnsFile.Name(),
expectedOutput: "foo",
},
{
name: "valid output format and invalid --template argument results in a parsing error from the printer",
outputFormat: "custom-columns",
templateArg: "invalid",
expectedError: "unexpected custom-columns spec: invalid, expected <header>:<json-path-expr>",
},
{
name: "no printer is matched on an invalid outputFormat",
outputFormat: "invalid",
expectNoMatch: true,
},
{
name: "custom-columns printer should not match on any other format supported by another printer",
outputFormat: "go-template",
expectNoMatch: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
printFlags := CustomColumnsPrintFlags{
TemplateArgument: tc.templateArg,
}
p, err := printFlags.ToPrinter(tc.outputFormat)
if tc.expectNoMatch {
if !genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
}
return
}
if genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
}
if len(tc.expectedError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
if len(tc.expectedParseError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedParseError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !strings.Contains(out.String(), tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}

View File

@ -1,372 +0,0 @@
/*
Copyright 2014 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 printers
import (
"bytes"
"reflect"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
)
func TestMassageJSONPath(t *testing.T) {
tests := []struct {
input string
expectedOutput string
expectErr bool
}{
{input: "foo.bar", expectedOutput: "{.foo.bar}"},
{input: "{foo.bar}", expectedOutput: "{.foo.bar}"},
{input: ".foo.bar", expectedOutput: "{.foo.bar}"},
{input: "{.foo.bar}", expectedOutput: "{.foo.bar}"},
{input: "", expectedOutput: ""},
{input: "{foo.bar", expectErr: true},
{input: "foo.bar}", expectErr: true},
{input: "{foo.bar}}", expectErr: true},
{input: "{{foo.bar}", expectErr: true},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
output, err := RelaxedJSONPathExpression(test.input)
if err != nil && !test.expectErr {
t.Errorf("unexpected error: %v", err)
return
}
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
}
return
}
if output != test.expectedOutput {
t.Errorf("input: %s, expected: %s, saw: %s", test.input, test.expectedOutput, output)
}
})
}
}
func TestNewColumnPrinterFromSpec(t *testing.T) {
tests := []struct {
spec string
expectedColumns []Column
expectErr bool
name string
noHeaders bool
}{
{
spec: "",
expectErr: true,
name: "empty",
},
{
spec: "invalid",
expectErr: true,
name: "invalid1",
},
{
spec: "invalid=foobar",
expectErr: true,
name: "invalid2",
},
{
spec: "invalid,foobar:blah",
expectErr: true,
name: "invalid3",
},
{
spec: "NAME:metadata.name,API_VERSION:apiVersion",
name: "ok",
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
},
{
spec: "API_VERSION:apiVersion",
name: "no-headers",
noHeaders: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
printer, err := NewCustomColumnsPrinterFromSpec(test.spec, legacyscheme.Codecs.UniversalDecoder(), test.noHeaders)
if test.expectErr {
if err == nil {
t.Errorf("[%s] unexpected non-error", test.name)
}
return
}
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
return
}
if test.noHeaders {
buffer := &bytes.Buffer{}
printer.PrintObj(&api.Pod{}, buffer)
if err != nil {
t.Fatalf("An error occurred printing Pod: %#v", err)
}
if contains(strings.Fields(buffer.String()), "API_VERSION") {
t.Errorf("unexpected header API_VERSION")
}
} else if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
}
})
}
}
func contains(arr []string, s string) bool {
for i := range arr {
if arr[i] == s {
return true
}
}
return false
}
const exampleTemplateOne = `NAME API_VERSION
{metadata.name} {apiVersion}`
const exampleTemplateTwo = `NAME API_VERSION
{metadata.name} {apiVersion}`
func TestNewColumnPrinterFromTemplate(t *testing.T) {
tests := []struct {
spec string
expectedColumns []Column
expectErr bool
name string
}{
{
spec: "",
expectErr: true,
name: "empty",
},
{
spec: "invalid",
expectErr: true,
name: "invalid1",
},
{
spec: "invalid=foobar",
expectErr: true,
name: "invalid2",
},
{
spec: "invalid,foobar:blah",
expectErr: true,
name: "invalid3",
},
{
spec: exampleTemplateOne,
name: "ok",
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
},
{
spec: exampleTemplateTwo,
name: "ok-2",
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
reader := bytes.NewBufferString(test.spec)
printer, err := NewCustomColumnsPrinterFromTemplate(reader, legacyscheme.Codecs.UniversalDecoder())
if test.expectErr {
if err == nil {
t.Errorf("[%s] unexpected non-error", test.name)
}
return
}
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
return
}
if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
}
})
}
}
func TestColumnPrint(t *testing.T) {
tests := []struct {
columns []Column
obj runtime.Object
expectedOutput string
}{
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
expectedOutput: `NAME
foo
`,
},
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
},
obj: &v1.PodList{
Items: []v1.Pod{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
},
},
expectedOutput: `NAME
foo
bar
`,
},
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
expectedOutput: `NAME API_VERSION
foo baz
`,
},
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
{
Header: "NOT_FOUND",
FieldSpec: "{.notFound}",
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
expectedOutput: `NAME API_VERSION NOT_FOUND
foo baz <none>
`,
},
}
for _, test := range tests {
t.Run(test.expectedOutput, func(t *testing.T) {
printer := &CustomColumnsPrinter{
Columns: test.columns,
Decoder: legacyscheme.Codecs.UniversalDecoder(),
}
buffer := &bytes.Buffer{}
if err := printer.PrintObj(test.obj, buffer); err != nil {
t.Errorf("unexpected error: %v", err)
}
if buffer.String() != test.expectedOutput {
t.Errorf("\nexpected:\n'%s'\nsaw\n'%s'\n", test.expectedOutput, buffer.String())
}
})
}
}
// this mimics how resource/get.go calls the customcolumn printer
func TestIndividualPrintObjOnExistingTabWriter(t *testing.T) {
columns := []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "LONG COLUMN NAME", // name is longer than all values of label1
FieldSpec: "{.metadata.labels.label1}",
},
{
Header: "LABEL 2",
FieldSpec: "{.metadata.labels.label2}",
},
}
objects := []*v1.Pod{
{ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"label1": "foo", "label2": "foo"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{"label1": "bar", "label2": "bar"}}},
}
expectedOutput := `NAME LONG COLUMN NAME LABEL 2
foo foo foo
bar bar bar
`
buffer := &bytes.Buffer{}
tabWriter := GetNewTabWriter(buffer)
printer := &CustomColumnsPrinter{
Columns: columns,
Decoder: legacyscheme.Codecs.UniversalDecoder(),
}
for _, obj := range objects {
if err := printer.PrintObj(obj, tabWriter); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
tabWriter.Flush()
if buffer.String() != expectedOutput {
t.Errorf("\nexpected:\n'%s'\nsaw\n'%s'\n", expectedOutput, buffer.String())
}
}

View File

@ -114,17 +114,25 @@ func (h *HumanReadablePrinter) EnsurePrintHeaders() {
// See ValidatePrintHandlerFunc for required method signature.
func (h *HumanReadablePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error {
var columnDefinitions []metav1beta1.TableColumnDefinition
for _, column := range columns {
for i, column := range columns {
format := ""
if i == 0 && strings.EqualFold(column, "name") {
format = "name"
}
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
Name: column,
Type: "string",
Name: column,
Description: column,
Type: "string",
Format: format,
})
}
for _, column := range columnsWithWide {
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
Name: column,
Type: "string",
Priority: 1,
Name: column,
Description: column,
Type: "string",
Priority: 1,
})
}
@ -363,6 +371,11 @@ func PrintTable(table *metav1beta1.Table, output io.Writer, options PrintOptions
for _, row := range table.Rows {
first := true
for i, cell := range row.Cells {
if i >= len(table.ColumnDefinitions) {
// https://issue.k8s.io/66379
// don't panic in case of bad output from the server, with more cells than column definitions
break
}
column := table.ColumnDefinitions[i]
if !options.Wide && column.Priority != 0 {
continue
@ -631,6 +644,13 @@ func (h *HumanReadablePrinter) legacyPrinterToTable(obj runtime.Object, handler
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(buf), reflect.ValueOf(options)}
if meta.IsListType(obj) {
listInterface, ok := obj.(metav1.ListInterface)
if ok {
table.ListMeta.SelfLink = listInterface.GetSelfLink()
table.ListMeta.ResourceVersion = listInterface.GetResourceVersion()
table.ListMeta.Continue = listInterface.GetContinue()
}
// 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)

View File

@ -17,7 +17,6 @@ limitations under the License.
package printers
import (
"fmt"
"io"
"k8s.io/apimachinery/pkg/runtime"
@ -58,36 +57,3 @@ type PrintOptions struct {
// indicates if it is OK to ignore missing keys for rendering an output template.
AllowMissingKeys bool
}
// Describer generates output for the named resource or an error
// if the output could not be generated. Implementers typically
// abstract the retrieval of the named object from a remote server.
type Describer interface {
Describe(namespace, name string, describerSettings DescriberSettings) (output string, err error)
}
// DescriberSettings holds display configuration for each object
// describer to control what is printed.
type DescriberSettings struct {
ShowEvents bool
}
// ObjectDescriber is an interface for displaying arbitrary objects with extra
// information. Use when an object is in hand (on disk, or already retrieved).
// Implementers may ignore the additional information passed on extra, or use it
// by default. ObjectDescribers may return ErrNoDescriber if no suitable describer
// is found.
type ObjectDescriber interface {
DescribeObject(object interface{}, extra ...interface{}) (output string, err error)
}
// ErrNoDescriber is a structured error indicating the provided object or objects
// cannot be described.
type ErrNoDescriber struct {
Types []string
}
// Error implements the error interface.
func (e ErrNoDescriber) Error() string {
return fmt.Sprintf("no describer has been defined for %v", e.Types)
}

View File

@ -9,7 +9,6 @@ load(
go_test(
name = "go_default_test",
srcs = [
"describe_test.go",
"printers_test.go",
"sorted_resource_name_list_test.go",
],
@ -20,99 +19,89 @@ go_test(
"//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/coordination:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/networking:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/scheduling:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/pointer:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality: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/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1: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",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/yaml:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"describe.go",
"import_known_versions.go",
"printers.go",
],
importpath = "k8s.io/kubernetes/pkg/printers/internalversion",
deps = [
"//pkg/api/events:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/ref:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/apps/install:go_default_library",
"//pkg/apis/authentication/install:go_default_library",
"//pkg/apis/authorization/install:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/autoscaling/install:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/batch/install:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/certificates/install:go_default_library",
"//pkg/apis/coordination:go_default_library",
"//pkg/apis/coordination/install:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/core/helper/qos:go_default_library",
"//pkg/apis/core/install:go_default_library",
"//pkg/apis/events/install:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/install:go_default_library",
"//pkg/apis/networking:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/policy/install:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/v1:go_default_library",
"//pkg/apis/rbac/install:go_default_library",
"//pkg/apis/scheduling:go_default_library",
"//pkg/apis/scheduling/install:go_default_library",
"//pkg/apis/settings/install:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/apis/storage/install:go_default_library",
"//pkg/apis/storage/util:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/fieldpath:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/slice:go_default_library",
"//vendor/github.com/fatih/camelcase:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors: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/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1: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/duration:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
"//staging/src/k8s.io/api/autoscaling/v2beta1:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/api/coordination/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/api/policy/v1beta1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1beta1:go_default_library",
"//staging/src/k8s.io/api/scheduling/v1beta1:go_default_library",
"//staging/src/k8s.io/api/storage/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
/*
Copyright 2016 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 internalversion
// These imports are the API groups the client will support.
// TODO: Remove these manual install once we don't need legacy scheme in get comman
import (
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authentication/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/certificates/install"
_ "k8s.io/kubernetes/pkg/apis/coordination/install"
_ "k8s.io/kubernetes/pkg/apis/core/install"
_ "k8s.io/kubernetes/pkg/apis/events/install"
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
_ "k8s.io/kubernetes/pkg/apis/policy/install"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
_ "k8s.io/kubernetes/pkg/apis/scheduling/install"
_ "k8s.io/kubernetes/pkg/apis/settings/install"
_ "k8s.io/kubernetes/pkg/apis/storage/install"
)

View File

@ -19,7 +19,6 @@ package internalversion
import (
"bytes"
"fmt"
"io"
"net"
"sort"
"strconv"
@ -31,9 +30,12 @@ import (
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
coordinationv1beta1 "k8s.io/api/coordination/v1beta1"
apiv1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
policyv1beta1 "k8s.io/api/policy/v1beta1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
schedulingv1beta1 "k8s.io/api/scheduling/v1beta1"
storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -43,17 +45,18 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api/events"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/apis/coordination"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/networking"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/apis/storage"
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
"k8s.io/kubernetes/pkg/printers"
@ -82,6 +85,8 @@ func AddHandlers(h printers.PrintHandler) {
{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"]},
{Name: "Nominated Node", Type: "string", Priority: 1, Description: apiv1.PodStatus{}.SwaggerDoc()["nominatedNodeName"]},
{Name: "Readiness Gates", Type: "string", Priority: 1, Description: apiv1.PodSpec{}.SwaggerDoc()["readinessGates"]},
}
h.TableHandler(podColumnDefinitions, printPodList)
h.TableHandler(podColumnDefinitions, printPod)
@ -149,8 +154,8 @@ func AddHandlers(h printers.PrintHandler) {
jobColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Desired", Type: "integer", Description: batchv1.JobSpec{}.SwaggerDoc()["completions"]},
{Name: "Successful", Type: "integer", Description: batchv1.JobStatus{}.SwaggerDoc()["succeeded"]},
{Name: "Completions", Type: "string", Description: batchv1.JobStatus{}.SwaggerDoc()["succeeded"]},
{Name: "Duration", Type: "string", Description: "Time required to complete the job."},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "Containers", Type: "string", Priority: 1, Description: "Names of each container in the template."},
{Name: "Images", Type: "string", Priority: 1, Description: "Images referenced by each container in the template."},
@ -198,8 +203,7 @@ func AddHandlers(h printers.PrintHandler) {
statefulSetColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Desired", Type: "string", Description: appsv1beta1.StatefulSetSpec{}.SwaggerDoc()["replicas"]},
{Name: "Current", Type: "string", Description: appsv1beta1.StatefulSetStatus{}.SwaggerDoc()["replicas"]},
{Name: "Ready", Type: "string", Description: "Number of the pod with ready state"},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "Containers", Type: "string", Priority: 1, Description: "Names of each container in the template."},
{Name: "Images", Type: "string", Priority: 1, Description: "Images referenced by each container in the template."},
@ -233,15 +237,15 @@ func AddHandlers(h printers.PrintHandler) {
eventColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Last Seen", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["lastTimestamp"]},
{Name: "First Seen", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["firstTimestamp"]},
{Name: "Count", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["count"]},
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Kind", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["kind"]},
{Name: "Subobject", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["fieldPath"]},
{Name: "Type", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["type"]},
{Name: "Reason", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["reason"]},
{Name: "Source", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["source"]},
{Name: "Kind", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["kind"]},
{Name: "Source", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["source"]},
{Name: "Message", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["message"]},
{Name: "Subobject", Type: "string", Priority: 1, Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["fieldPath"]},
{Name: "First Seen", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["firstTimestamp"]},
{Name: "Count", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["count"]},
{Name: "Name", Type: "string", Priority: 1, Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
}
h.TableHandler(eventColumnDefinitions, printEvent)
h.TableHandler(eventColumnDefinitions, printEventList)
@ -308,8 +312,7 @@ func AddHandlers(h printers.PrintHandler) {
deploymentColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Desired", Type: "string", Description: extensionsv1beta1.DeploymentSpec{}.SwaggerDoc()["replicas"]},
{Name: "Current", Type: "string", Description: extensionsv1beta1.DeploymentStatus{}.SwaggerDoc()["replicas"]},
{Name: "Ready", Type: "string", Description: "Number of the pod with ready state"},
{Name: "Up-to-date", Type: "string", Description: extensionsv1beta1.DeploymentStatus{}.SwaggerDoc()["updatedReplicas"]},
{Name: "Available", Type: "string", Description: extensionsv1beta1.DeploymentStatus{}.SwaggerDoc()["availableReplicas"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
@ -342,14 +345,14 @@ func AddHandlers(h printers.PrintHandler) {
podSecurityPolicyColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Priv", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["privileged"]},
{Name: "Caps", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["allowedCapabilities"]},
{Name: "SELinux", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["seLinux"]},
{Name: "RunAsUser", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["runAsUser"]},
{Name: "FsGroup", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["fsGroup"]},
{Name: "SupGroup", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["supplementalGroups"]},
{Name: "ReadOnlyRootFs", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["readOnlyRootFilesystem"]},
{Name: "Volumes", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["volumes"]},
{Name: "Priv", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["privileged"]},
{Name: "Caps", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["allowedCapabilities"]},
{Name: "SELinux", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["seLinux"]},
{Name: "RunAsUser", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["runAsUser"]},
{Name: "FsGroup", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["fsGroup"]},
{Name: "SupGroup", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["supplementalGroups"]},
{Name: "ReadOnlyRootFs", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["readOnlyRootFilesystem"]},
{Name: "Volumes", Type: "string", Description: policyv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["volumes"]},
}
h.TableHandler(podSecurityPolicyColumnDefinitions, printPodSecurityPolicy)
h.TableHandler(podSecurityPolicyColumnDefinitions, printPodSecurityPolicyList)
@ -393,6 +396,14 @@ func AddHandlers(h printers.PrintHandler) {
h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequest)
h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequestList)
leaseColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Holder", Type: "string", Description: coordinationv1beta1.LeaseSpec{}.SwaggerDoc()["holderIdentity"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
h.TableHandler(leaseColumnDefinitions, printLease)
h.TableHandler(leaseColumnDefinitions, printLeaseList)
storageClassColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Provisioner", Type: "string", Description: storagev1.StorageClass{}.SwaggerDoc()["provisioner"]},
@ -419,6 +430,24 @@ func AddHandlers(h printers.PrintHandler) {
h.TableHandler(controllerRevisionColumnDefinition, printControllerRevision)
h.TableHandler(controllerRevisionColumnDefinition, printControllerRevisionList)
resorceQuotaColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "Request", Type: "string", Description: "Request represents a minimum amount of cpu/memory that a container may consume."},
{Name: "Limit", Type: "string", Description: "Limits control the maximum amount of cpu/memory that a container may use independent of contention on the node."},
}
h.TableHandler(resorceQuotaColumnDefinitions, printResourceQuota)
h.TableHandler(resorceQuotaColumnDefinitions, printResourceQuotaList)
priorityClassColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Value", Type: "integer", Description: schedulingv1beta1.PriorityClass{}.SwaggerDoc()["value"]},
{Name: "Global-Default", Type: "boolean", Description: schedulingv1beta1.PriorityClass{}.SwaggerDoc()["globalDefault"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
h.TableHandler(priorityClassColumnDefinitions, printPriorityClass)
h.TableHandler(priorityClassColumnDefinitions, printPriorityClassList)
AddDefaultHandlers(h)
}
@ -457,7 +486,7 @@ func printObjectMeta(obj runtime.Object, options printers.PrintOptions) ([]metav
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, m.GetName(), translateTimestamp(m.GetCreationTimestamp()))
row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
rows = append(rows, row)
return rows, nil
}
@ -473,19 +502,33 @@ func formatEndpoints(endpoints *api.Endpoints, ports sets.String) string {
count := 0
for i := range endpoints.Subsets {
ss := &endpoints.Subsets[i]
for i := range ss.Ports {
port := &ss.Ports[i]
if ports == nil || ports.Has(port.Name) {
for i := range ss.Addresses {
if len(list) == max {
more = true
if len(ss.Ports) == 0 {
// It's possible to have headless services with no ports.
for i := range ss.Addresses {
if len(list) == max {
more = true
}
if !more {
list = append(list, ss.Addresses[i].IP)
}
count++
}
} else {
// "Normal" services with ports defined.
for i := range ss.Ports {
port := &ss.Ports[i]
if ports == nil || ports.Has(port.Name) {
for i := range ss.Addresses {
if len(list) == max {
more = true
}
addr := &ss.Addresses[i]
if !more {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port.Port)))
list = append(list, hostPort)
}
count++
}
addr := &ss.Addresses[i]
if !more {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port.Port)))
list = append(list, hostPort)
}
count++
}
}
}
@ -497,14 +540,24 @@ func formatEndpoints(endpoints *api.Endpoints, ports sets.String) string {
return ret
}
// translateTimestamp returns the elapsed time since timestamp in
// translateTimestampSince returns the elapsed time since timestamp in
// human-readable approximation.
func translateTimestamp(timestamp metav1.Time) string {
func translateTimestampSince(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.ShortHumanDuration(time.Since(timestamp.Time))
return duration.HumanDuration(time.Since(timestamp.Time))
}
// translateTimestampUntil returns the elapsed time until timestamp in
// human-readable approximation.
func translateTimestampUntil(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.HumanDuration(time.Until(timestamp.Time))
}
var (
@ -608,21 +661,39 @@ func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1beta1.TableR
reason = "Terminating"
}
row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, int64(restarts), translateTimestamp(pod.CreationTimestamp))
row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, int64(restarts), translateTimestampSince(pod.CreationTimestamp))
if options.Wide {
nodeName := pod.Spec.NodeName
nominatedNodeName := pod.Status.NominatedNodeName
podIP := pod.Status.PodIP
if podIP == "" {
podIP = "<none>"
}
if nodeName == "" {
nodeName = "<none>"
}
row.Cells = append(row.Cells, podIP, nodeName)
if len(pod.Status.NominatedNodeName) > 0 {
row.Cells = append(row.Cells, pod.Status.NominatedNodeName)
if nominatedNodeName == "" {
nominatedNodeName = "<none>"
}
readinessGates := "<none>"
if len(pod.Spec.ReadinessGates) > 0 {
trueConditions := 0
for _, readinessGate := range pod.Spec.ReadinessGates {
conditionType := readinessGate.ConditionType
for _, condition := range pod.Status.Conditions {
if condition.Type == conditionType {
if condition.Status == api.ConditionTrue {
trueConditions += 1
}
break
}
}
}
readinessGates = fmt.Sprintf("%d/%d", trueConditions, len(pod.Spec.ReadinessGates))
}
row.Cells = append(row.Cells, podIP, nodeName, nominatedNodeName, readinessGates)
}
return []metav1beta1.TableRow{row}, nil
@ -668,7 +739,7 @@ func printPodDisruptionBudget(obj *policy.PodDisruptionBudget, options printers.
maxUnavailable = "N/A"
}
row.Cells = append(row.Cells, obj.Name, minAvailable, maxUnavailable, int64(obj.Status.PodDisruptionsAllowed), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, minAvailable, maxUnavailable, int64(obj.Status.PodDisruptionsAllowed), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -694,7 +765,7 @@ func printReplicationController(obj *api.ReplicationController, options printers
currentReplicas := obj.Status.Replicas
readyReplicas := obj.Status.ReadyReplicas
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, labels.FormatLabels(obj.Spec.Selector))
@ -714,7 +785,7 @@ func printReplicationControllerList(list *api.ReplicationControllerList, options
return rows, nil
}
func printReplicaSet(obj *extensions.ReplicaSet, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printReplicaSet(obj *apps.ReplicaSet, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
@ -723,7 +794,7 @@ func printReplicaSet(obj *extensions.ReplicaSet, options printers.PrintOptions)
currentReplicas := obj.Status.Replicas
readyReplicas := obj.Status.ReadyReplicas
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.Selector))
@ -731,7 +802,7 @@ func printReplicaSet(obj *extensions.ReplicaSet, options printers.PrintOptions)
return []metav1beta1.TableRow{row}, nil
}
func printReplicaSetList(list *extensions.ReplicaSetList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printReplicaSetList(list *apps.ReplicaSetList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printReplicaSet(&list.Items[i], options)
@ -750,12 +821,28 @@ func printJob(obj *batch.Job, options printers.PrintOptions) ([]metav1beta1.Tabl
var completions string
if obj.Spec.Completions != nil {
completions = strconv.Itoa(int(*obj.Spec.Completions))
completions = fmt.Sprintf("%d/%d", obj.Status.Succeeded, *obj.Spec.Completions)
} else {
completions = "<none>"
parallelism := int32(0)
if obj.Spec.Parallelism != nil {
parallelism = *obj.Spec.Parallelism
}
if parallelism > 1 {
completions = fmt.Sprintf("%d/1 of %d", obj.Status.Succeeded, parallelism)
} else {
completions = fmt.Sprintf("%d/1", obj.Status.Succeeded)
}
}
var jobDuration string
switch {
case obj.Status.StartTime == nil:
case obj.Status.CompletionTime == nil:
jobDuration = duration.HumanDuration(time.Now().Sub(obj.Status.StartTime.Time))
default:
jobDuration = duration.HumanDuration(obj.Status.CompletionTime.Sub(obj.Status.StartTime.Time))
}
row.Cells = append(row.Cells, obj.Name, completions, int64(obj.Status.Succeeded), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, completions, jobDuration, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.Selector))
@ -782,10 +869,10 @@ func printCronJob(obj *batch.CronJob, options printers.PrintOptions) ([]metav1be
lastScheduleTime := "<none>"
if obj.Status.LastScheduleTime != nil {
lastScheduleTime = translateTimestamp(*obj.Status.LastScheduleTime)
lastScheduleTime = translateTimestampSince(*obj.Status.LastScheduleTime)
}
row.Cells = append(row.Cells, obj.Name, obj.Spec.Schedule, printBoolPtr(obj.Spec.Suspend), int64(len(obj.Status.Active)), lastScheduleTime, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, obj.Spec.Schedule, printBoolPtr(obj.Spec.Suspend), int64(len(obj.Status.Active)), lastScheduleTime, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.JobTemplate.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.JobTemplate.Spec.Selector))
@ -884,7 +971,7 @@ func printService(obj *api.Service, options printers.PrintOptions) ([]metav1beta
svcPorts = "<none>"
}
row.Cells = append(row.Cells, obj.Name, string(svcType), internalIP, externalIP, svcPorts, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(svcType), internalIP, externalIP, svcPorts, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
row.Cells = append(row.Cells, labels.FormatLabels(obj.Spec.Selector))
}
@ -948,7 +1035,7 @@ func printIngress(obj *extensions.Ingress, options printers.PrintOptions) ([]met
hosts := formatHosts(obj.Spec.Rules)
address := loadBalancerStatusStringer(obj.Status.LoadBalancer, options.Wide)
ports := formatPorts(obj.Spec.TLS)
createTime := translateTimestamp(obj.CreationTimestamp)
createTime := translateTimestampSince(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, hosts, address, ports, createTime)
return []metav1beta1.TableRow{row}, nil
}
@ -970,9 +1057,9 @@ func printStatefulSet(obj *apps.StatefulSet, options printers.PrintOptions) ([]m
Object: runtime.RawExtension{Object: obj},
}
desiredReplicas := obj.Spec.Replicas
currentReplicas := obj.Status.Replicas
createTime := translateTimestamp(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), createTime)
readyReplicas := obj.Status.ReadyReplicas
createTime := translateTimestampSince(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, fmt.Sprintf("%d/%d", int64(readyReplicas), int64(desiredReplicas)), createTime)
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images)
@ -992,7 +1079,7 @@ func printStatefulSetList(list *apps.StatefulSetList, options printers.PrintOpti
return rows, nil
}
func printDaemonSet(obj *extensions.DaemonSet, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printDaemonSet(obj *apps.DaemonSet, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
@ -1003,7 +1090,7 @@ func printDaemonSet(obj *extensions.DaemonSet, options printers.PrintOptions) ([
numberUpdated := obj.Status.UpdatedNumberScheduled
numberAvailable := obj.Status.NumberAvailable
row.Cells = append(row.Cells, obj.Name, int64(desiredScheduled), int64(currentScheduled), int64(numberReady), int64(numberUpdated), int64(numberAvailable), labels.FormatLabels(obj.Spec.Template.Spec.NodeSelector), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredScheduled), int64(currentScheduled), int64(numberReady), int64(numberUpdated), int64(numberAvailable), labels.FormatLabels(obj.Spec.Template.Spec.NodeSelector), translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.Selector))
@ -1011,7 +1098,7 @@ func printDaemonSet(obj *extensions.DaemonSet, options printers.PrintOptions) ([
return []metav1beta1.TableRow{row}, nil
}
func printDaemonSetList(list *extensions.DaemonSetList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printDaemonSetList(list *apps.DaemonSetList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printDaemonSet(&list.Items[i], options)
@ -1027,7 +1114,7 @@ func printEndpoints(obj *api.Endpoints, options printers.PrintOptions) ([]metav1
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, formatEndpoints(obj, nil), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, formatEndpoints(obj, nil), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1047,7 +1134,7 @@ func printNamespace(obj *api.Namespace, options printers.PrintOptions) ([]metav1
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, string(obj.Status.Phase), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(obj.Status.Phase), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1067,7 +1154,7 @@ func printSecret(obj *api.Secret, options printers.PrintOptions) ([]metav1beta1.
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, string(obj.Type), int64(len(obj.Data)), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(obj.Type), int64(len(obj.Data)), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1087,7 +1174,7 @@ func printServiceAccount(obj *api.ServiceAccount, options printers.PrintOptions)
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Secrets)), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Secrets)), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1136,7 +1223,7 @@ func printNode(obj *api.Node, options printers.PrintOptions) ([]metav1beta1.Tabl
roles = "<none>"
}
row.Cells = append(row.Cells, obj.Name, strings.Join(status, ","), roles, translateTimestamp(obj.CreationTimestamp), obj.Status.NodeInfo.KubeletVersion)
row.Cells = append(row.Cells, obj.Name, strings.Join(status, ","), roles, translateTimestampSince(obj.CreationTimestamp), obj.Status.NodeInfo.KubeletVersion)
if options.Wide {
osImage, kernelVersion, crVersion := obj.Status.NodeInfo.OSImage, obj.Status.NodeInfo.KernelVersion, obj.Status.NodeInfo.ContainerRuntimeVersion
if osImage == "" {
@ -1234,7 +1321,7 @@ func printPersistentVolume(obj *api.PersistentVolume, options printers.PrintOpti
row.Cells = append(row.Cells, obj.Name, aSize, modesStr, reclaimPolicyStr,
string(phase), claimRefUID, helper.GetPersistentVolumeClass(obj),
obj.Status.Reason,
translateTimestamp(obj.CreationTimestamp))
translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1269,7 +1356,7 @@ func printPersistentVolumeClaim(obj *api.PersistentVolumeClaim, options printers
capacity = storage.String()
}
row.Cells = append(row.Cells, obj.Name, string(phase), obj.Spec.VolumeName, capacity, accessModes, helper.GetPersistentVolumeClaimClass(obj), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(phase), obj.Spec.VolumeName, capacity, accessModes, helper.GetPersistentVolumeClaimClass(obj), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1290,25 +1377,42 @@ func printEvent(obj *api.Event, options printers.PrintOptions) ([]metav1beta1.Ta
Object: runtime.RawExtension{Object: obj},
}
// While watching event, we should print absolute time.
var FirstTimestamp, LastTimestamp string
var firstTimestamp, lastTimestamp string
if options.AbsoluteTimestamps {
FirstTimestamp = obj.FirstTimestamp.String()
LastTimestamp = obj.LastTimestamp.String()
firstTimestamp = obj.FirstTimestamp.String()
lastTimestamp = obj.LastTimestamp.String()
} else {
FirstTimestamp = translateTimestamp(obj.FirstTimestamp)
LastTimestamp = translateTimestamp(obj.LastTimestamp)
firstTimestamp = translateTimestampSince(obj.FirstTimestamp)
lastTimestamp = translateTimestampSince(obj.LastTimestamp)
}
if options.Wide {
row.Cells = append(row.Cells,
lastTimestamp,
obj.Type,
obj.Reason,
obj.InvolvedObject.Kind,
formatEventSource(obj.Source),
strings.TrimSpace(obj.Message),
obj.InvolvedObject.FieldPath,
firstTimestamp,
int64(obj.Count),
obj.Name,
)
} else {
row.Cells = append(row.Cells,
lastTimestamp,
obj.Type,
obj.Reason,
obj.InvolvedObject.Kind,
strings.TrimSpace(obj.Message),
)
}
row.Cells = append(row.Cells, LastTimestamp, FirstTimestamp,
int64(obj.Count), obj.Name, obj.InvolvedObject.Kind,
obj.InvolvedObject.FieldPath, obj.Type, obj.Reason,
formatEventSource(obj.Source), obj.Message)
return []metav1beta1.TableRow{row}, nil
}
// Sorts and prints the EventList in a human-friendly format.
func printEventList(list *api.EventList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
sort.Sort(events.SortableEvents(list.Items))
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printEvent(&list.Items[i], options)
@ -1325,7 +1429,7 @@ func printRoleBinding(obj *rbac.RoleBinding, options printers.PrintOptions) ([]m
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
roleRef := fmt.Sprintf("%s/%s", obj.RoleRef.Kind, obj.RoleRef.Name)
users, groups, sas, _ := rbac.SubjectsStrings(obj.Subjects)
@ -1352,7 +1456,7 @@ func printClusterRoleBinding(obj *rbac.ClusterRoleBinding, options printers.Prin
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
roleRef := fmt.Sprintf("%s/%s", obj.RoleRef.Kind, obj.RoleRef.Name)
users, groups, sas, _ := rbac.SubjectsStrings(obj.Subjects)
@ -1382,7 +1486,7 @@ func printCertificateSigningRequest(obj *certificates.CertificateSigningRequest,
if err != nil {
return nil, err
}
row.Cells = append(row.Cells, obj.Name, translateTimestamp(obj.CreationTimestamp), obj.Spec.Username, status)
row.Cells = append(row.Cells, obj.Name, translateTimestampSince(obj.CreationTimestamp), obj.Spec.Username, status)
return []metav1beta1.TableRow{row}, nil
}
@ -1460,29 +1564,22 @@ func printComponentStatusList(list *api.ComponentStatusList, options printers.Pr
return rows, nil
}
func truncate(str string, maxLen int) string {
if len(str) > maxLen {
return str[0:maxLen] + "..."
}
return str
}
func printDeployment(obj *extensions.Deployment, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printDeployment(obj *apps.Deployment, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
desiredReplicas := obj.Spec.Replicas
currentReplicas := obj.Status.Replicas
updatedReplicas := obj.Status.UpdatedReplicas
readyReplicas := obj.Status.ReadyReplicas
availableReplicas := obj.Status.AvailableReplicas
age := translateTimestamp(obj.CreationTimestamp)
age := translateTimestampSince(obj.CreationTimestamp)
containers := obj.Spec.Template.Spec.Containers
selector, err := metav1.LabelSelectorAsSelector(obj.Spec.Selector)
if err != nil {
// this shouldn't happen if LabelSelector passed validation
return nil, err
}
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(updatedReplicas), int64(availableReplicas), age)
row.Cells = append(row.Cells, obj.Name, fmt.Sprintf("%d/%d", int64(readyReplicas), int64(desiredReplicas)), int64(updatedReplicas), int64(availableReplicas), age)
if options.Wide {
containers, images := layoutContainerCells(containers)
row.Cells = append(row.Cells, containers, images, selector.String())
@ -1490,7 +1587,7 @@ func printDeployment(obj *extensions.Deployment, options printers.PrintOptions)
return []metav1beta1.TableRow{row}, nil
}
func printDeploymentList(list *extensions.DeploymentList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printDeploymentList(list *apps.DeploymentList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printDeployment(&list.Items[i], options)
@ -1513,47 +1610,47 @@ func formatHPAMetrics(specs []autoscaling.MetricSpec, statuses []autoscaling.Met
for i, spec := range specs {
switch spec.Type {
case autoscaling.ExternalMetricSourceType:
if spec.External.TargetAverageValue != nil {
if spec.External.Target.AverageValue != nil {
current := "<unknown>"
if len(statuses) > i && statuses[i].External != nil && statuses[i].External.CurrentAverageValue != nil {
current = statuses[i].External.CurrentAverageValue.String()
if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.Current.AverageValue != nil {
current = statuses[i].External.Current.AverageValue.String()
}
list = append(list, fmt.Sprintf("%s/%s (avg)", current, spec.External.TargetAverageValue.String()))
list = append(list, fmt.Sprintf("%s/%s (avg)", current, spec.External.Target.AverageValue.String()))
} else {
current := "<unknown>"
if len(statuses) > i && statuses[i].External != nil {
current = statuses[i].External.CurrentValue.String()
current = statuses[i].External.Current.Value.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.External.TargetValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.External.Target.Value.String()))
}
case autoscaling.PodsMetricSourceType:
current := "<unknown>"
if len(statuses) > i && statuses[i].Pods != nil {
current = statuses[i].Pods.CurrentAverageValue.String()
current = statuses[i].Pods.Current.AverageValue.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.Pods.TargetAverageValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.Pods.Target.AverageValue.String()))
case autoscaling.ObjectMetricSourceType:
current := "<unknown>"
if len(statuses) > i && statuses[i].Object != nil {
current = statuses[i].Object.CurrentValue.String()
current = statuses[i].Object.Current.Value.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.Object.TargetValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.Object.Target.Value.String()))
case autoscaling.ResourceMetricSourceType:
if spec.Resource.TargetAverageValue != nil {
if spec.Resource.Target.AverageValue != nil {
current := "<unknown>"
if len(statuses) > i && statuses[i].Resource != nil {
current = statuses[i].Resource.CurrentAverageValue.String()
current = statuses[i].Resource.Current.AverageValue.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.Resource.TargetAverageValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.Resource.Target.AverageValue.String()))
} else {
current := "<unknown>"
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil {
current = fmt.Sprintf("%d%%", *statuses[i].Resource.CurrentAverageUtilization)
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.Current.AverageUtilization != nil {
current = fmt.Sprintf("%d%%", *statuses[i].Resource.Current.AverageUtilization)
}
target := "<auto>"
if spec.Resource.TargetAverageUtilization != nil {
target = fmt.Sprintf("%d%%", *spec.Resource.TargetAverageUtilization)
if spec.Resource.Target.AverageUtilization != nil {
target = fmt.Sprintf("%d%%", *spec.Resource.Target.AverageUtilization)
}
list = append(list, fmt.Sprintf("%s/%s", current, target))
}
@ -1591,7 +1688,7 @@ func printHorizontalPodAutoscaler(obj *autoscaling.HorizontalPodAutoscaler, opti
}
maxPods := obj.Spec.MaxReplicas
currentReplicas := obj.Status.CurrentReplicas
row.Cells = append(row.Cells, obj.Name, reference, metrics, minPods, int64(maxPods), int64(currentReplicas), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, reference, metrics, minPods, int64(maxPods), int64(currentReplicas), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1611,7 +1708,7 @@ func printConfigMap(obj *api.ConfigMap, options printers.PrintOptions) ([]metav1
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Data)), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Data)), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1664,7 +1761,7 @@ func printNetworkPolicy(obj *networking.NetworkPolicy, options printers.PrintOpt
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, metav1.FormatLabelSelector(&obj.Spec.PodSelector), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, metav1.FormatLabelSelector(&obj.Spec.PodSelector), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1690,7 +1787,7 @@ func printStorageClass(obj *storage.StorageClass, options printers.PrintOptions)
name += " (default)"
}
provtype := obj.Provisioner
row.Cells = append(row.Cells, name, provtype, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, name, provtype, translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1707,6 +1804,31 @@ func printStorageClassList(list *storage.StorageClassList, options printers.Prin
return rows, nil
}
func printLease(obj *coordination.Lease, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
var holderIdentity string
if obj.Spec.HolderIdentity != nil {
holderIdentity = *obj.Spec.HolderIdentity
}
row.Cells = append(row.Cells, obj.Name, holderIdentity, translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
func printLeaseList(list *coordination.LeaseList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printLease(&list.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printStatus(obj *metav1.Status, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
@ -1716,24 +1838,6 @@ func printStatus(obj *metav1.Status, options printers.PrintOptions) ([]metav1bet
return []metav1beta1.TableRow{row}, nil
}
// Lay out all the containers on one line if use wide output.
// DEPRECATED: convert to TableHandler and use layoutContainerCells
func layoutContainers(containers []api.Container, w io.Writer) error {
var namesBuffer bytes.Buffer
var imagesBuffer bytes.Buffer
for i, container := range containers {
namesBuffer.WriteString(container.Name)
imagesBuffer.WriteString(container.Image)
if i != len(containers)-1 {
namesBuffer.WriteString(",")
imagesBuffer.WriteString(",")
}
}
_, err := fmt.Fprintf(w, "\t%s\t%s", namesBuffer.String(), imagesBuffer.String())
return err
}
// Lay out all the containers on one line if use wide output.
func layoutContainerCells(containers []api.Container) (names string, images string) {
var namesBuffer bytes.Buffer
@ -1776,7 +1880,7 @@ func printControllerRevision(obj *apps.ControllerRevision, options printers.Prin
controllerName = printers.FormatResourceName(gvk.GroupKind(), controllerRef.Name, withKind)
}
revision := obj.Revision
age := translateTimestamp(obj.CreationTimestamp)
age := translateTimestampSince(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, controllerName, revision, age)
return []metav1beta1.TableRow{row}, nil
}
@ -1792,3 +1896,102 @@ func printControllerRevisionList(list *apps.ControllerRevisionList, options prin
}
return rows, nil
}
func printResourceQuota(resourceQuota *api.ResourceQuota, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: resourceQuota},
}
resources := make([]api.ResourceName, 0, len(resourceQuota.Status.Hard))
for resource := range resourceQuota.Status.Hard {
resources = append(resources, resource)
}
sort.Sort(SortableResourceNames(resources))
requestColumn := bytes.NewBuffer([]byte{})
limitColumn := bytes.NewBuffer([]byte{})
for i := range resources {
w := requestColumn
resource := resources[i]
usedQuantity := resourceQuota.Status.Used[resource]
hardQuantity := resourceQuota.Status.Hard[resource]
// use limitColumn writer if a resource name prefixed with "limits" is found
if pieces := strings.Split(resource.String(), "."); len(pieces) > 1 && pieces[0] == "limits" {
w = limitColumn
}
fmt.Fprintf(w, "%s: %s/%s, ", resource, usedQuantity.String(), hardQuantity.String())
}
age := translateTimestampSince(resourceQuota.CreationTimestamp)
row.Cells = append(row.Cells, resourceQuota.Name, age, strings.TrimSuffix(requestColumn.String(), ", "), strings.TrimSuffix(limitColumn.String(), ", "))
return []metav1beta1.TableRow{row}, nil
}
func printResourceQuotaList(list *api.ResourceQuotaList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printResourceQuota(&list.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printPriorityClass(obj *scheduling.PriorityClass, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
name := obj.Name
value := obj.Value
globalDefault := obj.GlobalDefault
row.Cells = append(row.Cells, name, int64(value), globalDefault, translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
func printPriorityClassList(list *scheduling.PriorityClassList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printPriorityClass(&list.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printBoolPtr(value *bool) string {
if value != nil {
return printBool(*value)
}
return "<unset>"
}
func printBool(value bool) string {
if value {
return "True"
}
return "False"
}
type SortableResourceNames []api.ResourceName
func (list SortableResourceNames) Len() int {
return len(list)
}
func (list SortableResourceNames) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
func (list SortableResourceNames) Less(i, j int) bool {
return list[i] < list[j]
}

View File

@ -28,7 +28,7 @@ import (
"testing"
"time"
"github.com/ghodss/yaml"
"sigs.k8s.io/yaml"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
@ -41,25 +41,22 @@ import (
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
genericprinters "k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/coordination"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/printers"
)
func init() {
legacyscheme.Scheme.AddKnownTypes(schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}, &TestPrintType{})
legacyscheme.Scheme.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &TestPrintType{})
}
var testData = TestStruct{
TypeMeta: metav1.TypeMeta{APIVersion: "foo/bar", Kind: "TestStruct"},
Key: "testValue",
@ -256,8 +253,12 @@ func testPrinter(t *testing.T, printer printers.ResourcePrinter, unmarshalFunc f
}
}
func yamlUnmarshal(data []byte, v interface{}) error {
return yaml.Unmarshal(data, v)
}
func TestYAMLPrinter(t *testing.T) {
testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), yaml.Unmarshal)
testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), yamlUnmarshal)
}
func TestJSONPrinter(t *testing.T) {
@ -1151,6 +1152,10 @@ func TestPrintHumanReadableService(t *testing.T) {
Port: 8000,
Protocol: "TCP",
},
{
Port: 7777,
Protocol: "SCTP",
},
},
},
},
@ -1288,7 +1293,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "foo/bar",
Image: "foo/bar",
TerminationMessagePath: api.TerminationMessagePathDefault,
ImagePullPolicy: api.PullIfNotPresent,
},
@ -1644,6 +1649,9 @@ func TestPrintPod(t *testing.T) {
}
func TestPrintPodwide(t *testing.T) {
condition1 := "condition1"
condition2 := "condition2"
condition3 := "condition3"
tests := []struct {
pod api.Pod
expect []metav1beta1.TableRow
@ -1655,8 +1663,29 @@ func TestPrintPodwide(t *testing.T) {
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: "test1",
ReadinessGates: []api.PodReadinessGate{
{
ConditionType: api.PodConditionType(condition1),
},
{
ConditionType: api.PodConditionType(condition2),
},
{
ConditionType: api.PodConditionType(condition3),
},
},
},
Status: api.PodStatus{
Conditions: []api.PodCondition{
{
Type: api.PodConditionType(condition1),
Status: api.ConditionFalse,
},
{
Type: api.PodConditionType(condition2),
Status: api.ConditionTrue,
},
},
Phase: "podPhase",
PodIP: "1.1.1.1",
ContainerStatuses: []api.ContainerStatus{
@ -1666,7 +1695,7 @@ func TestPrintPodwide(t *testing.T) {
NominatedNodeName: "node1",
},
},
[]metav1beta1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", int64(6), "<unknown>", "1.1.1.1", "test1", "node1"}}},
[]metav1beta1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", int64(6), "<unknown>", "1.1.1.1", "test1", "node1", "1/3"}}},
},
{
// Test when the NodeName and PodIP are none
@ -1685,7 +1714,7 @@ func TestPrintPodwide(t *testing.T) {
},
},
},
[]metav1beta1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", int64(6), "<unknown>", "<none>", "<none>"}}},
[]metav1beta1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", int64(6), "<unknown>", "<none>", "<none>", "<none>", "<none>"}}},
},
}
@ -1918,18 +1947,43 @@ type stringTestList []struct {
name, got, exp string
}
func TestTranslateTimestamp(t *testing.T) {
func TestTranslateTimestampSince(t *testing.T) {
tl := stringTestList{
{"a while from now", translateTimestamp(metav1.Time{Time: time.Now().Add(2.1e9)}), "<invalid>"},
{"almost now", translateTimestamp(metav1.Time{Time: time.Now().Add(1.9e9)}), "0s"},
{"now", translateTimestamp(metav1.Time{Time: time.Now()}), "0s"},
{"unknown", translateTimestamp(metav1.Time{}), "<unknown>"},
{"30 seconds ago", translateTimestamp(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"},
{"5 minutes ago", translateTimestamp(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"},
{"an hour ago", translateTimestamp(metav1.Time{Time: time.Now().Add(-6e12)}), "1h"},
{"2 days ago", translateTimestamp(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"},
{"months ago", translateTimestamp(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"},
{"10 years ago", translateTimestamp(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"},
{"a while from now", translateTimestampSince(metav1.Time{Time: time.Now().Add(2.1e9)}), "<invalid>"},
{"almost now", translateTimestampSince(metav1.Time{Time: time.Now().Add(1.9e9)}), "0s"},
{"now", translateTimestampSince(metav1.Time{Time: time.Now()}), "0s"},
{"unknown", translateTimestampSince(metav1.Time{}), "<unknown>"},
{"30 seconds ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"},
{"5 minutes ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"},
{"an hour ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-6e12)}), "100m"},
{"2 days ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"},
{"months ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"},
{"10 years ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"},
}
for _, test := range tl {
if test.got != test.exp {
t.Errorf("On %v, expected '%v', but got '%v'",
test.name, test.exp, test.got)
}
}
}
func TestTranslateTimestampUntil(t *testing.T) {
// Since this method compares the time with time.Now() internally,
// small buffers of 0.1 seconds are added on comparing times to consider method call overhead.
// Otherwise, the output strings become shorter than expected.
const buf = 1e8
tl := stringTestList{
{"a while ago", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-2.1e9)}), "<invalid>"},
{"almost now", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-1.9e9)}), "0s"},
{"now", translateTimestampUntil(metav1.Time{Time: time.Now()}), "0s"},
{"unknown", translateTimestampUntil(metav1.Time{}), "<unknown>"},
{"in 30 seconds", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e10 + buf)}), "30s"},
{"in 5 minutes", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e11 + buf)}), "5m"},
{"in an hour", translateTimestampUntil(metav1.Time{Time: time.Now().Add(6e12 + buf)}), "100m"},
{"in 2 days", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 2).Add(buf)}), "2d"},
{"in months", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 90).Add(buf)}), "90d"},
{"in 10 years", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0).Add(buf)}), "10y"},
}
for _, test := range tl {
if test.got != test.exp {
@ -1941,17 +1995,17 @@ func TestTranslateTimestamp(t *testing.T) {
func TestPrintDeployment(t *testing.T) {
tests := []struct {
deployment extensions.Deployment
deployment apps.Deployment
expect string
wideExpect string
}{
{
extensions.Deployment{
apps.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Spec: extensions.DeploymentSpec{
Spec: apps.DeploymentSpec{
Replicas: 5,
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
@ -1969,15 +2023,15 @@ func TestPrintDeployment(t *testing.T) {
},
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
Status: extensions.DeploymentStatus{
Status: apps.DeploymentStatus{
Replicas: 10,
UpdatedReplicas: 2,
AvailableReplicas: 1,
UnavailableReplicas: 4,
},
},
"test1\t5\t10\t2\t1\t0s\n",
"test1\t5\t10\t2\t1\t0s\tfake-container1,fake-container2\tfake-image1,fake-image2\tfoo=bar\n",
"test1\t0/5\t2\t1\t0s\n",
"test1\t0/5\t2\t1\t0s\tfake-container1,fake-container2\tfake-image1,fake-image2\tfoo=bar\n",
},
}
@ -2010,21 +2064,21 @@ func TestPrintDeployment(t *testing.T) {
func TestPrintDaemonSet(t *testing.T) {
tests := []struct {
ds extensions.DaemonSet
ds apps.DaemonSet
startsWith string
}{
{
extensions.DaemonSet{
apps.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Spec: extensions.DaemonSetSpec{
Spec: apps.DaemonSetSpec{
Template: api.PodTemplateSpec{
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
},
},
Status: extensions.DaemonSetStatus{
Status: apps.DaemonSetStatus{
CurrentNumberScheduled: 2,
DesiredNumberScheduled: 3,
NumberReady: 1,
@ -2054,6 +2108,7 @@ func TestPrintDaemonSet(t *testing.T) {
}
func TestPrintJob(t *testing.T) {
now := time.Now()
completions := int32(2)
tests := []struct {
job batch.Job
@ -2072,7 +2127,7 @@ func TestPrintJob(t *testing.T) {
Succeeded: 1,
},
},
"job1\t2\t1\t0s\n",
"job1\t1/2\t\t0s\n",
},
{
batch.Job{
@ -2087,7 +2142,40 @@ func TestPrintJob(t *testing.T) {
Succeeded: 0,
},
},
"job2\t<none>\t0\t10y\n",
"job2\t0/1\t\t10y\n",
},
{
batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job3",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: batch.JobSpec{
Completions: nil,
},
Status: batch.JobStatus{
Succeeded: 0,
StartTime: &metav1.Time{Time: now.Add(time.Minute)},
CompletionTime: &metav1.Time{Time: now.Add(31 * time.Minute)},
},
},
"job3\t0/1\t30m\t10y\n",
},
{
batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job4",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: batch.JobSpec{
Completions: nil,
},
Status: batch.JobStatus{
Succeeded: 0,
StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)},
},
},
"job4\t0/1\t20m\t10y\n",
},
}
@ -2112,6 +2200,10 @@ func TestPrintHPA(t *testing.T) {
minReplicasVal := int32(2)
targetUtilizationVal := int32(80)
currentUtilizationVal := int32(50)
metricLabelSelector, err := metav1.ParseToLabelSelector("label=value")
if err != nil {
t.Errorf("unable to parse label selector: %v", err)
}
tests := []struct {
hpa autoscaling.HorizontalPodAutoscaler
expected string
@ -2149,13 +2241,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@ -2182,13 +2275,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@ -2200,13 +2294,13 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricStatus{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
MetricName: "some-external-metric",
CurrentAverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
@ -2229,13 +2323,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-service-metric",
TargetValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@ -2262,13 +2357,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@ -2280,8 +2376,12 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricStatus{
MetricName: "some-external-metric",
CurrentValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
},
Current: autoscaling.MetricValueStatus{
Value: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@ -2304,8 +2404,13 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@ -2332,8 +2437,13 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@ -2345,8 +2455,12 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricStatus{
MetricName: "some-pods-metric",
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@ -2369,12 +2483,17 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricSource{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
TargetValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@ -2401,12 +2520,17 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricSource{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
TargetValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@ -2418,12 +2542,16 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricStatus{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
CurrentValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Current: autoscaling.MetricValueStatus{
Value: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@ -2446,8 +2574,11 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
Name: api.ResourceCPU,
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@ -2474,8 +2605,11 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
Name: api.ResourceCPU,
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@ -2487,8 +2621,10 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Name: api.ResourceCPU,
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@ -2512,7 +2648,10 @@ func TestPrintHPA(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
@ -2540,7 +2679,10 @@ func TestPrintHPA(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
@ -2553,8 +2695,10 @@ func TestPrintHPA(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageUtilization: &currentUtilizationVal,
CurrentAverageValue: *resource.NewMilliQuantity(40, resource.DecimalSI),
Current: autoscaling.MetricValueStatus{
AverageUtilization: &currentUtilizationVal,
AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
},
},
},
},
@ -2577,22 +2721,35 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "other-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(400, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "other-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI),
},
},
},
},
@ -2604,16 +2761,22 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricStatus{
MetricName: "some-pods-metric",
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageUtilization: &currentUtilizationVal,
CurrentAverageValue: *resource.NewMilliQuantity(40, resource.DecimalSI),
Current: autoscaling.MetricValueStatus{
AverageUtilization: &currentUtilizationVal,
AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
},
},
},
},
@ -3015,17 +3178,17 @@ func boolP(b bool) *bool {
func TestPrintReplicaSet(t *testing.T) {
tests := []struct {
replicaSet extensions.ReplicaSet
replicaSet apps.ReplicaSet
expect string
wideExpect string
}{
{
extensions.ReplicaSet{
apps.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Spec: extensions.ReplicaSetSpec{
Spec: apps.ReplicaSetSpec{
Replicas: 5,
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
@ -3043,7 +3206,7 @@ func TestPrintReplicaSet(t *testing.T) {
},
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
Status: extensions.ReplicaSetStatus{
Status: apps.ReplicaSetStatus{
Replicas: 5,
ReadyReplicas: 2,
},
@ -3299,6 +3462,100 @@ func TestPrintStorageClass(t *testing.T) {
}
}
func TestPrintLease(t *testing.T) {
holder1 := "holder1"
holder2 := "holder2"
tests := []struct {
sc coordination.Lease
expect string
}{
{
coordination.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: "lease1",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Spec: coordination.LeaseSpec{
HolderIdentity: &holder1,
},
},
"lease1\tholder1\t0s\n",
},
{
coordination.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: "lease2",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
},
Spec: coordination.LeaseSpec{
HolderIdentity: &holder2,
},
},
"lease2\tholder2\t5m\n",
},
}
buf := bytes.NewBuffer([]byte{})
for _, test := range tests {
table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.sc, printers.PrintOptions{})
if err != nil {
t.Fatal(err)
}
if err := printers.PrintTable(table, buf, printers.PrintOptions{NoHeaders: true}); err != nil {
t.Fatal(err)
}
if buf.String() != test.expect {
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String())
}
buf.Reset()
}
}
func TestPrintPriorityClass(t *testing.T) {
tests := []struct {
pc scheduling.PriorityClass
expect string
}{
{
scheduling.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: "pc1",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Value: 1,
},
"pc1\t1\tfalse\t0s\n",
},
{
scheduling.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: "pc2",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
},
Value: 1000000000,
GlobalDefault: true,
},
"pc2\t1000000000\ttrue\t5m\n",
},
}
buf := bytes.NewBuffer([]byte{})
for _, test := range tests {
table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.pc, printers.PrintOptions{})
if err != nil {
t.Fatal(err)
}
verifyTable(t, table)
if err := printers.PrintTable(table, buf, printers.PrintOptions{NoHeaders: true}); err != nil {
t.Fatal(err)
}
if buf.String() != test.expect {
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String())
}
buf.Reset()
}
}
func verifyTable(t *testing.T, table *metav1beta1.Table) {
var panicErr interface{}
func() {
@ -3312,3 +3569,27 @@ func verifyTable(t *testing.T, table *metav1beta1.Table) {
t.Errorf("unexpected panic during deepcopy of table %#v: %v", table, panicErr)
}
}
// VerifyDatesInOrder checks the start of each line for a RFC1123Z date
// and posts error if all subsequent dates are not equal or increasing
func VerifyDatesInOrder(
resultToTest, rowDelimiter, columnDelimiter string, t *testing.T) {
lines := strings.Split(resultToTest, rowDelimiter)
var previousTime time.Time
for _, str := range lines {
columns := strings.Split(str, columnDelimiter)
if len(columns) > 0 {
currentTime, err := time.Parse(time.RFC1123Z, columns[0])
if err == nil {
if previousTime.After(currentTime) {
t.Errorf(
"Output is not sorted by time. %s should be listed after %s. Complete output: %s",
previousTime.Format(time.RFC1123Z),
currentTime.Format(time.RFC1123Z),
resultToTest)
}
previousTime = currentTime
}
}
}
}

View File

@ -11,8 +11,8 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/printers/storage",
deps = [
"//pkg/printers:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)

View File

@ -22,7 +22,7 @@ import (
)
const (
tabwriterMinWidth = 10
tabwriterMinWidth = 6
tabwriterWidth = 4
tabwriterPadding = 3
tabwriterPadChar = ' '