vendor update for CSI 0.3.0

This commit is contained in:
gman
2018-07-18 16:47:22 +02:00
parent 6f484f92fc
commit 8ea659f0d5
6810 changed files with 438061 additions and 193861 deletions

View File

@ -10,20 +10,17 @@ go_library(
name = "go_default_library",
srcs = [
"customcolumn.go",
"customcolumn_flags.go",
"humanreadable.go",
"interface.go",
"json.go",
"jsonpath.go",
"name.go",
"printers.go",
"tabwriter.go",
"template.go",
"versioned.go",
],
importpath = "k8s.io/kubernetes/pkg/printers",
deps = [
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//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",
@ -31,25 +28,11 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["customcolumn_test.go"],
deps = [
":go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core: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/runtime:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
@ -69,10 +52,17 @@ filegroup(
go_test(
name = "go_default_test",
srcs = ["humanreadable_test.go"],
srcs = [
"customcolumn_flags_test.go",
"customcolumn_test.go",
"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",

View File

@ -29,14 +29,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/jsonpath"
)
const (
columnwidth = 10
tabwidth = 4
padding = 3
padding_character = ' '
flags = 0
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
)
var jsonRegexp = regexp.MustCompile("^\\{\\.?([^{}]+)\\}$|^\\.?([^{}]+)$")
@ -158,12 +151,19 @@ type CustomColumnsPrinter struct {
lastType reflect.Type
}
func (s *CustomColumnsPrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
w := tabwriter.NewWriter(out, columnwidth, tabwidth, padding, padding_character, flags)
// 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 {
@ -171,7 +171,7 @@ func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error
for ix := range s.Columns {
headers[ix] = s.Columns[ix].Header
}
fmt.Fprintln(w, strings.Join(headers, "\t"))
fmt.Fprintln(out, strings.Join(headers, "\t"))
s.lastType = t
}
parsers := make([]*jsonpath.JSONPath, len(s.Columns))
@ -188,16 +188,16 @@ func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error
return err
}
for ix := range objs {
if err := s.printOneObject(objs[ix], parsers, w); err != nil {
if err := s.printOneObject(objs[ix], parsers, out); err != nil {
return err
}
}
} else {
if err := s.printOneObject(obj, parsers, w); err != nil {
if err := s.printOneObject(obj, parsers, out); err != nil {
return err
}
}
return w.Flush()
return nil
}
func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jsonpath.JSONPath, out io.Writer) error {
@ -240,11 +240,3 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
fmt.Fprintln(out, strings.Join(columns, "\t"))
return nil
}
func (s *CustomColumnsPrinter) HandledResources() []string {
return []string{}
}
func (s *CustomColumnsPrinter) IsGeneric() bool {
return true
}

View File

@ -0,0 +1,109 @@
/*
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

@ -0,0 +1,139 @@
/*
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

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package printers_test
package printers
import (
"bytes"
@ -27,7 +27,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/printers"
)
func TestMassageJSONPath(t *testing.T) {
@ -47,27 +46,29 @@ func TestMassageJSONPath(t *testing.T) {
{input: "{{foo.bar}", expectErr: true},
}
for _, test := range tests {
output, err := printers.RelaxedJSONPathExpression(test.input)
if err != nil && !test.expectErr {
t.Errorf("unexpected error: %v", err)
continue
}
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
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
}
continue
}
if output != test.expectedOutput {
t.Errorf("input: %s, expected: %s, saw: %s", test.input, test.expectedOutput, output)
}
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 []printers.Column
expectedColumns []Column
expectErr bool
name string
noHeaders bool
@ -95,7 +96,7 @@ func TestNewColumnPrinterFromSpec(t *testing.T) {
{
spec: "NAME:metadata.name,API_VERSION:apiVersion",
name: "ok",
expectedColumns: []printers.Column{
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
@ -113,33 +114,34 @@ func TestNewColumnPrinterFromSpec(t *testing.T) {
},
}
for _, test := range tests {
printer, err := printers.NewCustomColumnsPrinterFromSpec(test.spec, legacyscheme.Codecs.UniversalDecoder(), test.noHeaders)
if test.expectErr {
if err == nil {
t.Errorf("[%s] unexpected non-error", test.name)
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
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
continue
}
if test.noHeaders {
buffer := &bytes.Buffer{}
printer.PrintObj(&api.Pod{}, buffer)
if err != nil {
t.Fatalf("An error occurred printing Pod: %#v", err)
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
return
}
if test.noHeaders {
buffer := &bytes.Buffer{}
if contains(strings.Fields(buffer.String()), "API_VERSION") {
t.Errorf("unexpected header API_VERSION")
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)
}
} else if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
}
})
}
}
@ -161,7 +163,7 @@ const exampleTemplateTwo = `NAME API_VERSION
func TestNewColumnPrinterFromTemplate(t *testing.T) {
tests := []struct {
spec string
expectedColumns []printers.Column
expectedColumns []Column
expectErr bool
name string
}{
@ -188,7 +190,7 @@ func TestNewColumnPrinterFromTemplate(t *testing.T) {
{
spec: exampleTemplateOne,
name: "ok",
expectedColumns: []printers.Column{
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
@ -202,7 +204,7 @@ func TestNewColumnPrinterFromTemplate(t *testing.T) {
{
spec: exampleTemplateTwo,
name: "ok-2",
expectedColumns: []printers.Column{
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
@ -215,34 +217,35 @@ func TestNewColumnPrinterFromTemplate(t *testing.T) {
},
}
for _, test := range tests {
reader := bytes.NewBufferString(test.spec)
printer, err := printers.NewCustomColumnsPrinterFromTemplate(reader, legacyscheme.Codecs.UniversalDecoder())
if test.expectErr {
if err == nil {
t.Errorf("[%s] unexpected non-error", test.name)
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
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
continue
}
if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
}
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 []printers.Column
columns []Column
obj runtime.Object
expectedOutput string
}{
{
columns: []printers.Column{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
@ -254,7 +257,7 @@ foo
`,
},
{
columns: []printers.Column{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
@ -272,7 +275,7 @@ bar
`,
},
{
columns: []printers.Column{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
@ -288,7 +291,7 @@ foo baz
`,
},
{
columns: []printers.Column{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
@ -310,16 +313,60 @@ foo baz <none>
}
for _, test := range tests {
printer := &printers.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())
}
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

@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -70,11 +71,10 @@ var _ PrintHandler = &HumanReadablePrinter{}
// NewHumanReadablePrinter creates a HumanReadablePrinter.
// If encoder and decoder are provided, an attempt to convert unstructured types to internal types is made.
func NewHumanReadablePrinter(encoder runtime.Encoder, decoder runtime.Decoder, options PrintOptions) *HumanReadablePrinter {
func NewHumanReadablePrinter(decoder runtime.Decoder, options PrintOptions) *HumanReadablePrinter {
printer := &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
options: options,
encoder: encoder,
decoder: decoder,
}
return printer
@ -101,19 +101,6 @@ func (a *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePri
return a
}
// GetResourceKind returns the type currently set for a resource
func (h *HumanReadablePrinter) GetResourceKind() string {
return h.options.Kind
}
// EnsurePrintWithKind sets HumanReadablePrinter options "WithKind" to true
// and "Kind" to the string arg it receives, pre-pending this string
// to the "NAME" column in an output of resources.
func (h *HumanReadablePrinter) EnsurePrintWithKind(kind string) {
h.options.WithKind = true
h.options.Kind = kind
}
// EnsurePrintHeaders sets the HumanReadablePrinter option "NoHeaders" to false
// and removes the .lastType that was printed, which forces headers to be
// printed in cases where multiple lists of the same resource are printed
@ -270,14 +257,6 @@ func (h *HumanReadablePrinter) HandledResources() []string {
return keys
}
func (h *HumanReadablePrinter) AfterPrint(output io.Writer, res string) error {
return nil
}
func (h *HumanReadablePrinter) IsGeneric() bool {
return false
}
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
_, err := fmt.Fprintf(w, "Unknown object: %s", string(data))
return err
@ -291,12 +270,9 @@ func printHeader(columnNames []string, w io.Writer) error {
}
// PrintObj prints the obj in a human-friendly format according to the type of the obj.
// TODO: unify the behavior of PrintObj, which often expects single items and tracks
// headers and filtering, with other printers, that expect list objects. The tracking
// behavior should probably be a higher level wrapper (MultiObjectTablePrinter) that
// calls into the PrintTable method and then displays consistent output.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
if w, found := output.(*tabwriter.Writer); !found && !h.skipTabWriter {
w, found := output.(*tabwriter.Writer)
if !found && !h.skipTabWriter {
w = GetNewTabWriter(output)
output = w
defer w.Flush()
@ -312,14 +288,19 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before
// trying to print, since the printers are keyed by type. This is extremely expensive.
if h.encoder != nil && h.decoder != nil {
obj, _ = decodeUnknownObject(obj, h.encoder, h.decoder)
if h.decoder != nil {
obj, _ = decodeUnknownObject(obj, h.decoder)
}
// print with a registered handler
t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil {
includeHeaders := h.lastType != t && !h.options.NoHeaders
if h.lastType != nil && h.lastType != t && !h.options.NoHeaders {
fmt.Fprintln(output)
}
if err := printRowsForHandlerEntry(output, handler, obj, h.options, includeHeaders); err != nil {
return err
}
@ -330,6 +311,11 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
// print with the default handler if set, and use the columns from the last time
if h.defaultHandler != nil {
includeHeaders := h.lastType != h.defaultHandler && !h.options.NoHeaders
if h.lastType != nil && h.lastType != h.defaultHandler && !h.options.NoHeaders {
fmt.Fprintln(output)
}
if err := printRowsForHandlerEntry(output, h.defaultHandler, obj, h.options, includeHeaders); err != nil {
return err
}
@ -375,9 +361,6 @@ func PrintTable(table *metav1beta1.Table, output io.Writer, options PrintOptions
fmt.Fprintln(output)
}
for _, row := range table.Rows {
if !options.ShowAll && hasCondition(row.Conditions, metav1beta1.RowCompleted) {
continue
}
first := true
for i, cell := range row.Cells {
column := table.ColumnDefinitions[i]
@ -414,7 +397,7 @@ func DecorateTable(table *metav1beta1.Table, options PrintOptions) error {
columns := table.ColumnDefinitions
nameColumn := -1
if options.WithKind && len(options.Kind) > 0 {
if options.WithKind && !options.Kind.Empty() {
for i := range columns {
if columns[i].Format == "name" && columns[i].Type == "string" {
nameColumn = i
@ -454,7 +437,7 @@ func DecorateTable(table *metav1beta1.Table, options PrintOptions) error {
row := rows[i]
if nameColumn != -1 {
row.Cells[nameColumn] = fmt.Sprintf("%s/%s", options.Kind, row.Cells[nameColumn])
row.Cells[nameColumn] = fmt.Sprintf("%s/%s", strings.ToLower(options.Kind.String()), row.Cells[nameColumn])
}
var m metav1.Object
@ -610,8 +593,8 @@ func printRows(output io.Writer, rows []metav1beta1.TableRow, options PrintOptio
fmt.Fprint(output, "\t")
} else {
// TODO: remove this once we drop the legacy printers
if options.WithKind && len(options.Kind) > 0 {
fmt.Fprintf(output, "%s/%s", options.Kind, cell)
if options.WithKind && !options.Kind.Empty() {
fmt.Fprintf(output, "%s/%s", strings.ToLower(options.Kind.String()), cell)
continue
}
}
@ -788,12 +771,12 @@ func appendLabelCells(values []interface{}, itemLabels map[string]string, opts P
// FormatResourceName receives a resource kind, name, and boolean specifying
// whether or not to update the current name to "kind/name"
func FormatResourceName(kind, name string, withKind bool) string {
if !withKind || kind == "" {
func FormatResourceName(kind schema.GroupKind, name string, withKind bool) string {
if !withKind || kind.Empty() {
return name
}
return kind + "/" + name
return strings.ToLower(kind.String()) + "/" + name
}
func AppendLabels(itemLabels map[string]string, columnLabels []string) string {
@ -826,15 +809,19 @@ func AppendAllLabels(showLabels bool, itemLabels map[string]string) string {
}
// check if the object is unstructured. If so, attempt to convert it to a type we can understand.
func decodeUnknownObject(obj runtime.Object, encoder runtime.Encoder, decoder runtime.Decoder) (runtime.Object, error) {
func decodeUnknownObject(obj runtime.Object, decoder runtime.Decoder) (runtime.Object, error) {
var err error
switch obj.(type) {
case runtime.Unstructured, *runtime.Unknown:
if objBytes, err := runtime.Encode(encoder, obj); err == nil {
switch t := obj.(type) {
case runtime.Unstructured:
if objBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj); err == nil {
if decodedObj, err := runtime.Decode(decoder, objBytes); err == nil {
obj = decodedObj
}
}
case *runtime.Unknown:
if decodedObj, err := runtime.Decode(decoder, t.Raw); err == nil {
obj = decodedObj
}
}
return obj, err

View File

@ -48,7 +48,8 @@ func testPrintNamespace(obj *api.Namespace, options PrintOptions) ([]metav1beta1
func TestPrintRowsForHandlerEntry(t *testing.T) {
printFunc := reflect.ValueOf(testPrintNamespace)
testCase := map[string]struct {
testCase := []struct {
name string
h *handlerEntry
opt PrintOptions
obj runtime.Object
@ -56,7 +57,8 @@ func TestPrintRowsForHandlerEntry(t *testing.T) {
expectOut string
expectErr string
}{
"no tablecolumndefinition and includeheader flase": {
{
name: "no tablecolumndefinition and includeheader flase",
h: &handlerEntry{
columnDefinitions: []metav1beta1.TableColumnDefinition{},
printRows: true,
@ -69,7 +71,8 @@ func TestPrintRowsForHandlerEntry(t *testing.T) {
includeHeader: false,
expectOut: "test\t\t<unknow>\n",
},
"no tablecolumndefinition and includeheader true": {
{
name: "no tablecolumndefinition and includeheader true",
h: &handlerEntry{
columnDefinitions: []metav1beta1.TableColumnDefinition{},
printRows: true,
@ -82,7 +85,8 @@ func TestPrintRowsForHandlerEntry(t *testing.T) {
includeHeader: true,
expectOut: "\ntest\t\t<unknow>\n",
},
"have tablecolumndefinition and includeheader true": {
{
name: "have tablecolumndefinition and includeheader true",
h: &handlerEntry{
columnDefinitions: testNamespaceColumnDefinitions,
printRows: true,
@ -95,7 +99,8 @@ func TestPrintRowsForHandlerEntry(t *testing.T) {
includeHeader: true,
expectOut: "NAME\tSTATUS\tAGE\ntest\t\t<unknow>\n",
},
"print namespace and withnamespace true, should not print header": {
{
name: "print namespace and withnamespace true, should not print header",
h: &handlerEntry{
columnDefinitions: testNamespaceColumnDefinitions,
printRows: true,
@ -112,16 +117,18 @@ func TestPrintRowsForHandlerEntry(t *testing.T) {
expectErr: "namespace is not namespaced",
},
}
for name, test := range testCase {
buffer := &bytes.Buffer{}
err := printRowsForHandlerEntry(buffer, test.h, test.obj, test.opt, test.includeHeader)
if err != nil {
if err.Error() != test.expectErr {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", name, test.expectErr, err)
for _, test := range testCase {
t.Run(test.name, func(t *testing.T) {
buffer := &bytes.Buffer{}
err := printRowsForHandlerEntry(buffer, test.h, test.obj, test.opt, test.includeHeader)
if err != nil {
if err.Error() != test.expectErr {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, test.expectErr, err)
}
}
}
if test.expectOut != buffer.String() {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", name, test.expectOut, buffer.String())
}
if test.expectOut != buffer.String() {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, test.expectOut, buffer.String())
}
})
}
}

View File

@ -21,18 +21,13 @@ import (
"io"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// ResourcePrinter is an interface that knows how to print runtime objects.
type ResourcePrinter interface {
// Print receives a runtime object, formats it and prints it to a writer.
PrintObj(runtime.Object, io.Writer) error
HandledResources() []string
//Can be used to print out warning/clarifications if needed
//after all objects were printed
AfterPrint(io.Writer, string) error
// Identify if it is a generic printer
IsGeneric() bool
}
// ResourcePrinterFunc is a function that can print objects
@ -43,19 +38,6 @@ func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error {
return fn(obj, w)
}
// TODO: implement HandledResources()
func (fn ResourcePrinterFunc) HandledResources() []string {
return []string{}
}
func (fn ResourcePrinterFunc) AfterPrint(io.Writer, string) error {
return nil
}
func (fn ResourcePrinterFunc) IsGeneric() bool {
return true
}
type PrintOptions struct {
// supported Format types can be found in pkg/printers/printers.go
OutputFormatType string
@ -68,7 +50,7 @@ type PrintOptions struct {
ShowAll bool
ShowLabels bool
AbsoluteTimestamps bool
Kind string
Kind schema.GroupKind
ColumnLabels []string
SortBy string

View File

@ -27,11 +27,13 @@ go_test(
"//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/api/extensions/v1beta1: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",
@ -70,6 +72,7 @@ go_library(
"//pkg/apis/networking:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/v1:go_default_library",
"//pkg/apis/scheduling:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/apis/storage/util:go_default_library",
@ -83,6 +86,7 @@ go_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",
@ -90,6 +94,7 @@ go_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",
@ -106,7 +111,8 @@ go_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/typed/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)

View File

@ -33,7 +33,9 @@ import (
"github.com/golang/glog"
"github.com/fatih/camelcase"
versionedextension "k8s.io/api/extensions/v1beta1"
appsv1 "k8s.io/api/apps/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
@ -45,7 +47,8 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/dynamic"
clientextensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
externalclient "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/events"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/ref"
@ -61,6 +64,7 @@ import (
"k8s.io/kubernetes/pkg/apis/networking"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
"k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/apis/storage"
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
@ -122,7 +126,16 @@ func (pw *prefixWriter) Flush() {
}
}
func describerMap(c clientset.Interface) map[schema.GroupKind]printers.Describer {
func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]printers.Describer, error) {
c, err := clientset.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
externalclient, err := externalclient.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
m := map[schema.GroupKind]printers.Describer{
api.Kind("Pod"): &PodDescriber{c},
api.Kind("ReplicationController"): &ReplicationControllerDescriber{c},
@ -144,43 +157,38 @@ func describerMap(c clientset.Interface) map[schema.GroupKind]printers.Describer
extensions.Kind("PodSecurityPolicy"): &PodSecurityPolicyDescriber{c},
autoscaling.Kind("HorizontalPodAutoscaler"): &HorizontalPodAutoscalerDescriber{c},
extensions.Kind("DaemonSet"): &DaemonSetDescriber{c},
extensions.Kind("Deployment"): &DeploymentDescriber{c, versionedExtensionsClientV1beta1(c)},
extensions.Kind("Deployment"): &DeploymentDescriber{c, externalclient},
extensions.Kind("Ingress"): &IngressDescriber{c},
batch.Kind("Job"): &JobDescriber{c},
batch.Kind("CronJob"): &CronJobDescriber{c},
batch.Kind("CronJob"): &CronJobDescriber{c, externalclient},
apps.Kind("StatefulSet"): &StatefulSetDescriber{c},
apps.Kind("Deployment"): &DeploymentDescriber{c, versionedExtensionsClientV1beta1(c)},
apps.Kind("Deployment"): &DeploymentDescriber{c, externalclient},
apps.Kind("DaemonSet"): &DaemonSetDescriber{c},
apps.Kind("ReplicaSet"): &ReplicaSetDescriber{c},
certificates.Kind("CertificateSigningRequest"): &CertificateSigningRequestDescriber{c},
storage.Kind("StorageClass"): &StorageClassDescriber{c},
policy.Kind("PodDisruptionBudget"): &PodDisruptionBudgetDescriber{c},
rbac.Kind("Role"): &RoleDescriber{c},
rbac.Kind("ClusterRole"): &ClusterRoleDescriber{c},
rbac.Kind("RoleBinding"): &RoleBindingDescriber{c},
rbac.Kind("ClusterRoleBinding"): &ClusterRoleBindingDescriber{c},
rbac.Kind("Role"): &RoleDescriber{externalclient},
rbac.Kind("ClusterRole"): &ClusterRoleDescriber{externalclient},
rbac.Kind("RoleBinding"): &RoleBindingDescriber{externalclient},
rbac.Kind("ClusterRoleBinding"): &ClusterRoleBindingDescriber{externalclient},
networking.Kind("NetworkPolicy"): &NetworkPolicyDescriber{c},
scheduling.Kind("PriorityClass"): &PriorityClassDescriber{c},
}
return m
}
// DescribableResources lists all resource types we can describe.
func DescribableResources() []string {
keys := make([]string, 0)
for k := range describerMap(nil) {
resource := strings.ToLower(k.Kind)
keys = append(keys, resource)
}
return keys
return m, nil
}
// DescriberFor returns the default describe functions for each of the standard
// Kubernetes types.
func DescriberFor(kind schema.GroupKind, c clientset.Interface) (printers.Describer, bool) {
f, ok := describerMap(c)[kind]
func DescriberFor(kind schema.GroupKind, clientConfig *rest.Config) (printers.Describer, bool) {
describers, err := describerMap(clientConfig)
if err != nil {
glog.V(1).Info(err)
return nil, false
}
f, ok := describers[kind]
return f, ok
}
@ -197,12 +205,7 @@ type genericDescriber struct {
}
func (g *genericDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (output string, err error) {
apiResource := &metav1.APIResource{
Name: g.mapping.Resource,
Namespaced: g.mapping.Scope.Name() == meta.RESTScopeNameNamespace,
Kind: g.mapping.GroupVersionKind.Kind,
}
obj, err := g.dynamic.Resource(apiResource, namespace).Get(name, metav1.GetOptions{})
obj, err := g.dynamic.Resource(g.mapping.Resource).Namespace(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return "", err
}
@ -963,7 +966,17 @@ func printCinderVolumeSource(cinder *api.CinderVolumeSource, w PrefixWriter) {
" VolumeID:\t%v\n"+
" FSType:\t%v\n"+
" ReadOnly:\t%v\n",
cinder.VolumeID, cinder.FSType, cinder.ReadOnly)
" SecretRef:\t%v\n"+
cinder.VolumeID, cinder.FSType, cinder.ReadOnly, cinder.SecretRef)
}
func printCinderPersistentVolumeSource(cinder *api.CinderPersistentVolumeSource, w PrefixWriter) {
w.Write(LEVEL_2, "Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+
" VolumeID:\t%v\n"+
" FSType:\t%v\n"+
" ReadOnly:\t%v\n",
" SecretRef:\t%v\n"+
cinder.VolumeID, cinder.SecretRef, cinder.FSType, cinder.ReadOnly, cinder.SecretRef)
}
func printScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, w PrefixWriter) {
@ -1225,7 +1238,7 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) (
case pv.Spec.VsphereVolume != nil:
printVsphereVolumeSource(pv.Spec.VsphereVolume, w)
case pv.Spec.Cinder != nil:
printCinderVolumeSource(pv.Spec.Cinder, w)
printCinderPersistentVolumeSource(pv.Spec.Cinder, w)
case pv.Spec.AzureDisk != nil:
printAzureDiskVolumeSource(pv.Spec.AzureDisk, w)
case pv.Spec.PhotonPersistentDisk != nil:
@ -1307,6 +1320,20 @@ func describePersistentVolumeClaim(pvc *api.PersistentVolumeClaim, events *api.E
if pvc.Spec.VolumeMode != nil {
w.Write(LEVEL_0, "VolumeMode:\t%v\n", *pvc.Spec.VolumeMode)
}
if len(pvc.Status.Conditions) > 0 {
w.Write(LEVEL_0, "Conditions:\n")
w.Write(LEVEL_1, "Type\tStatus\tLastProbeTime\tLastTransitionTime\tReason\tMessage\n")
w.Write(LEVEL_1, "----\t------\t-----------------\t------------------\t------\t-------\n")
for _, c := range pvc.Status.Conditions {
w.Write(LEVEL_1, "%v \t%v \t%s \t%s \t%v \t%v\n",
c.Type,
c.Status,
c.LastProbeTime.Time.Format(time.RFC1123Z),
c.LastTransitionTime.Time.Format(time.RFC1123Z),
c.Reason,
c.Message)
}
}
if events != nil {
DescribeEvents(events, w)
}
@ -1840,10 +1867,11 @@ func describeJob(job *batch.Job, events *api.EventList) (string, error) {
// CronJobDescriber generates information about a cron job and the jobs it has created.
type CronJobDescriber struct {
clientset.Interface
external externalclient.Interface
}
func (d *CronJobDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) {
cronJob, err := d.Batch().CronJobs(namespace).Get(name, metav1.GetOptions{})
cronJob, err := d.external.BatchV1beta1().CronJobs(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return "", err
}
@ -1853,7 +1881,12 @@ func (d *CronJobDescriber) Describe(namespace, name string, describerSettings pr
events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, cronJob)
}
return describeCronJob(cronJob, events)
internalCronJob := &batch.CronJob{}
if err := legacyscheme.Scheme.Convert(cronJob, internalCronJob, nil); err != nil {
return "", err
}
return describeCronJob(internalCronJob, events)
}
func describeCronJob(cronJob *batch.CronJob, events *api.EventList) (string, error) {
@ -2422,7 +2455,7 @@ func describeServiceAccount(serviceAccount *api.ServiceAccount, tokens []api.Sec
// RoleDescriber generates information about a node.
type RoleDescriber struct {
clientset.Interface
externalclient.Interface
}
func (d *RoleDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) {
@ -2431,7 +2464,7 @@ func (d *RoleDescriber) Describe(namespace, name string, describerSettings print
return "", err
}
breakdownRules := []rbac.PolicyRule{}
breakdownRules := []rbacv1.PolicyRule{}
for _, rule := range role.Rules {
breakdownRules = append(breakdownRules, validation.BreakdownRule(rule)...)
}
@ -2440,7 +2473,7 @@ func (d *RoleDescriber) Describe(namespace, name string, describerSettings print
if err != nil {
return "", err
}
sort.Stable(rbac.SortableRuleSlice(compactRules))
sort.Stable(rbacv1helpers.SortableRuleSlice(compactRules))
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
@ -2461,7 +2494,7 @@ func (d *RoleDescriber) Describe(namespace, name string, describerSettings print
// ClusterRoleDescriber generates information about a node.
type ClusterRoleDescriber struct {
clientset.Interface
externalclient.Interface
}
func (d *ClusterRoleDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) {
@ -2470,7 +2503,7 @@ func (d *ClusterRoleDescriber) Describe(namespace, name string, describerSetting
return "", err
}
breakdownRules := []rbac.PolicyRule{}
breakdownRules := []rbacv1.PolicyRule{}
for _, rule := range role.Rules {
breakdownRules = append(breakdownRules, validation.BreakdownRule(rule)...)
}
@ -2479,7 +2512,7 @@ func (d *ClusterRoleDescriber) Describe(namespace, name string, describerSetting
if err != nil {
return "", err
}
sort.Stable(rbac.SortableRuleSlice(compactRules))
sort.Stable(rbacv1helpers.SortableRuleSlice(compactRules))
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
@ -2517,7 +2550,7 @@ func combineResourceGroup(resource, group []string) string {
// RoleBindingDescriber generates information about a node.
type RoleBindingDescriber struct {
clientset.Interface
externalclient.Interface
}
func (d *RoleBindingDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) {
@ -2549,7 +2582,7 @@ func (d *RoleBindingDescriber) Describe(namespace, name string, describerSetting
// ClusterRoleBindingDescriber generates information about a node.
type ClusterRoleBindingDescriber struct {
clientset.Interface
externalclient.Interface
}
func (d *ClusterRoleBindingDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) {
@ -2689,9 +2722,6 @@ func describeNode(node *api.Node, nodeNonTerminatedPodsList *api.PodList, events
if len(node.Spec.PodCIDR) > 0 {
w.Write(LEVEL_0, "PodCIDR:\t%s\n", node.Spec.PodCIDR)
}
if len(node.Spec.ExternalID) > 0 {
w.Write(LEVEL_0, "ExternalID:\t%s\n", node.Spec.ExternalID)
}
if len(node.Spec.ProviderID) > 0 {
w.Write(LEVEL_0, "ProviderID:\t%s\n", node.Spec.ProviderID)
}
@ -2746,6 +2776,14 @@ func describeStatefulSet(ps *apps.StatefulSet, selector labels.Selector, events
printLabelsMultiline(w, "Labels", ps.Labels)
printAnnotationsMultiline(w, "Annotations", ps.Annotations)
w.Write(LEVEL_0, "Replicas:\t%d desired | %d total\n", ps.Spec.Replicas, ps.Status.Replicas)
w.Write(LEVEL_0, "Update Strategy:\t%s\n", ps.Spec.UpdateStrategy.Type)
if ps.Spec.UpdateStrategy.RollingUpdate != nil {
ru := ps.Spec.UpdateStrategy.RollingUpdate
if ru.Partition != 0 {
w.Write(LEVEL_1, "Partition:\t%d\n", ru.Partition)
}
}
w.Write(LEVEL_0, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed)
DescribePodTemplate(&ps.Spec.Template, w)
describeVolumeClaimTemplates(ps.Spec.VolumeClaimTemplates, w)
@ -2924,17 +2962,9 @@ func describeHorizontalPodAutoscaler(hpa *autoscaling.HorizontalPodAutoscaler, e
}
w.Write(LEVEL_0, "Min replicas:\t%s\n", minReplicas)
w.Write(LEVEL_0, "Max replicas:\t%d\n", hpa.Spec.MaxReplicas)
w.Write(LEVEL_0, "%s pods:\t", hpa.Spec.ScaleTargetRef.Kind)
w.Write(LEVEL_0, "%d current / %d desired\n", hpa.Status.CurrentReplicas, hpa.Status.DesiredReplicas)
// TODO: switch to scale subresource once the required code is submitted.
if strings.ToLower(hpa.Spec.ScaleTargetRef.Kind) == "replicationcontroller" {
w.Write(LEVEL_0, "ReplicationController pods:\t")
rc, err := d.client.Core().ReplicationControllers(hpa.Namespace).Get(hpa.Spec.ScaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
w.Write(LEVEL_0, "%d current / %d desired\n", rc.Status.Replicas, rc.Spec.Replicas)
} else {
w.Write(LEVEL_0, "failed to check Replication Controller\n")
}
}
if len(hpa.Status.Conditions) > 0 {
w.Write(LEVEL_0, "Conditions:\n")
w.Write(LEVEL_1, "Type\tStatus\tReason\tMessage\n")
@ -2973,8 +3003,9 @@ func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node
memoryReq.String(), int64(fractionMemoryReq), memoryLimit.String(), int64(fractionMemoryLimit))
}
w.Write(LEVEL_0, "Allocated resources:\n (Total limits may be over 100 percent, i.e., overcommitted.)\n CPU Requests\tCPU Limits\tMemory Requests\tMemory Limits\n")
w.Write(LEVEL_1, "------------\t----------\t---------------\t-------------\n")
w.Write(LEVEL_0, "Allocated resources:\n (Total limits may be over 100 percent, i.e., overcommitted.)\n")
w.Write(LEVEL_1, "Resource\tRequests\tLimits\n")
w.Write(LEVEL_1, "--------\t--------\t------\n")
reqs, limits := getPodsTotalRequestsAndLimits(nodeNonTerminatedPodsList)
cpuReqs, cpuLimits, memoryReqs, memoryLimits := reqs[api.ResourceCPU], limits[api.ResourceCPU], reqs[api.ResourceMemory], limits[api.ResourceMemory]
fractionCpuReqs := float64(0)
@ -2989,9 +3020,21 @@ func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node
fractionMemoryReqs = float64(memoryReqs.Value()) / float64(allocatable.Memory().Value()) * 100
fractionMemoryLimits = float64(memoryLimits.Value()) / float64(allocatable.Memory().Value()) * 100
}
w.Write(LEVEL_1, "%s (%d%%)\t%s (%d%%)\t%s (%d%%)\t%s (%d%%)\n",
cpuReqs.String(), int64(fractionCpuReqs), cpuLimits.String(), int64(fractionCpuLimits),
memoryReqs.String(), int64(fractionMemoryReqs), memoryLimits.String(), int64(fractionMemoryLimits))
w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n",
api.ResourceCPU, cpuReqs.String(), int64(fractionCpuReqs), cpuLimits.String(), int64(fractionCpuLimits))
w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n",
api.ResourceMemory, memoryReqs.String(), int64(fractionMemoryReqs), memoryLimits.String(), int64(fractionMemoryLimits))
extResources := make([]string, 0, len(allocatable))
for resource := range allocatable {
if !helper.IsStandardContainerResourceName(string(resource)) && resource != api.ResourcePods {
extResources = append(extResources, string(resource))
}
}
sort.Strings(extResources)
for _, ext := range extResources {
extRequests, extLimits := reqs[api.ResourceName(ext)], limits[api.ResourceName(ext)]
w.Write(LEVEL_1, "%s\t%s\t%s\n", ext, extRequests.String(), extLimits.String())
}
}
func getPodsTotalRequestsAndLimits(podList *api.PodList) (reqs map[api.ResourceName]resource.Quantity, limits map[api.ResourceName]resource.Quantity) {
@ -3047,11 +3090,11 @@ func DescribeEvents(el *api.EventList, w PrefixWriter) {
// DeploymentDescriber generates information about a deployment.
type DeploymentDescriber struct {
clientset.Interface
extensionV1beta1Client clientextensionsv1beta1.ExtensionsV1beta1Interface
external externalclient.Interface
}
func (dd *DeploymentDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) {
d, err := dd.extensionV1beta1Client.Deployments(namespace).Get(name, metav1.GetOptions{})
d, err := dd.external.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return "", err
}
@ -3072,7 +3115,7 @@ func (dd *DeploymentDescriber) Describe(namespace, name string, describerSetting
return describeDeployment(d, selector, internalDeployment, events, dd)
}
func describeDeployment(d *versionedextension.Deployment, selector labels.Selector, internalDeployment *extensions.Deployment, events *api.EventList, dd *DeploymentDescriber) (string, error) {
func describeDeployment(d *appsv1.Deployment, selector labels.Selector, internalDeployment *extensions.Deployment, events *api.EventList, dd *DeploymentDescriber) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", d.ObjectMeta.Name)
@ -3096,10 +3139,10 @@ func describeDeployment(d *versionedextension.Deployment, selector labels.Select
w.Write(LEVEL_1, "%v \t%v\t%v\n", c.Type, c.Status, c.Reason)
}
}
oldRSs, _, newRS, err := deploymentutil.GetAllReplicaSets(d, dd.extensionV1beta1Client)
oldRSs, _, newRS, err := deploymentutil.GetAllReplicaSets(d, dd.external.AppsV1())
if err == nil {
w.Write(LEVEL_0, "OldReplicaSets:\t%s\n", printReplicaSetsByLabels(oldRSs))
var newRSs []*versionedextension.ReplicaSet
var newRSs []*appsv1.ReplicaSet
if newRS != nil {
newRSs = append(newRSs, newRS)
}
@ -3113,7 +3156,7 @@ func describeDeployment(d *versionedextension.Deployment, selector labels.Select
})
}
func printReplicaSetsByLabels(matchingRSs []*versionedextension.ReplicaSet) string {
func printReplicaSetsByLabels(matchingRSs []*appsv1.ReplicaSet) string {
// Format the matching ReplicaSets into strings.
rsStrings := make([]string, 0, len(matchingRSs))
for _, rs := range matchingRSs {
@ -3258,15 +3301,18 @@ func printNetworkPolicySpecIngressFrom(npirs []networking.NetworkPolicyIngressRu
w.Write(LEVEL_0, "%s%s\n", initialIndent, "From: <any> (traffic not restricted by source)")
} else {
for _, from := range npir.From {
w.Write(LEVEL_0, "%s", initialIndent)
if from.PodSelector != nil {
w.Write(LEVEL_0, "%s: %s\n", "From PodSelector", metav1.FormatLabelSelector(from.PodSelector))
w.Write(LEVEL_0, "%s%s\n", initialIndent, "From:")
if from.PodSelector != nil && from.NamespaceSelector != nil {
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector))
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(from.PodSelector))
} else if from.PodSelector != nil {
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(from.PodSelector))
} else if from.NamespaceSelector != nil {
w.Write(LEVEL_0, "%s: %s\n", "From NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector))
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector))
} else if from.IPBlock != nil {
w.Write(LEVEL_0, "From IPBlock:\n")
w.Write(LEVEL_0, "%s%sCIDR: %s\n", initialIndent, initialIndent, from.IPBlock.CIDR)
w.Write(LEVEL_0, "%s%sExcept: %v\n", initialIndent, initialIndent, strings.Join(from.IPBlock.Except, ", "))
w.Write(LEVEL_1, "%sIPBlock:\n", initialIndent)
w.Write(LEVEL_2, "%sCIDR: %s\n", initialIndent, from.IPBlock.CIDR)
w.Write(LEVEL_2, "%sExcept: %v\n", initialIndent, strings.Join(from.IPBlock.Except, ", "))
}
}
}
@ -3299,15 +3345,18 @@ func printNetworkPolicySpecEgressTo(npers []networking.NetworkPolicyEgressRule,
w.Write(LEVEL_0, "%s%s\n", initialIndent, "To: <any> (traffic not restricted by source)")
} else {
for _, to := range nper.To {
w.Write(LEVEL_0, "%s", initialIndent)
if to.PodSelector != nil {
w.Write(LEVEL_0, "%s: %s\n", "To PodSelector", metav1.FormatLabelSelector(to.PodSelector))
w.Write(LEVEL_0, "%s%s\n", initialIndent, "To:")
if to.PodSelector != nil && to.NamespaceSelector != nil {
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector))
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(to.PodSelector))
} else if to.PodSelector != nil {
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(to.PodSelector))
} else if to.NamespaceSelector != nil {
w.Write(LEVEL_0, "%s: %s\n", "To NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector))
w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector))
} else if to.IPBlock != nil {
w.Write(LEVEL_0, "To IPBlock:\n")
w.Write(LEVEL_0, "%s%sCIDR: %s\n", initialIndent, initialIndent, to.IPBlock.CIDR)
w.Write(LEVEL_0, "%s%sExcept: %v\n", initialIndent, initialIndent, strings.Join(to.IPBlock.Except, ", "))
w.Write(LEVEL_1, "%sIPBlock:\n", initialIndent)
w.Write(LEVEL_2, "%sCIDR: %s\n", initialIndent, to.IPBlock.CIDR)
w.Write(LEVEL_2, "%sExcept: %v\n", initialIndent, strings.Join(to.IPBlock.Except, ", "))
}
}
}
@ -3456,7 +3505,7 @@ type PodSecurityPolicyDescriber struct {
}
func (d *PodSecurityPolicyDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) {
psp, err := d.Extensions().PodSecurityPolicies().Get(name, metav1.GetOptions{})
psp, err := d.Policy().PodSecurityPolicies().Get(name, metav1.GetOptions{})
if err != nil {
return "", err
}
@ -3464,7 +3513,7 @@ func (d *PodSecurityPolicyDescriber) Describe(namespace, name string, describerS
return describePodSecurityPolicy(psp)
}
func describePodSecurityPolicy(psp *extensions.PodSecurityPolicy) (string, error) {
func describePodSecurityPolicy(psp *policy.PodSecurityPolicy) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", psp.Name)
@ -3501,13 +3550,13 @@ func describePodSecurityPolicy(psp *extensions.PodSecurityPolicy) (string, error
w.Write(LEVEL_2, "Level:\t%s\n", stringOrNone(level))
w.Write(LEVEL_1, "Run As User Strategy: %s\t\n", string(psp.Spec.RunAsUser.Rule))
w.Write(LEVEL_2, "Ranges:\t%s\n", userIDRangeToString(psp.Spec.RunAsUser.Ranges))
w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.RunAsUser.Ranges))
w.Write(LEVEL_1, "FSGroup Strategy: %s\t\n", string(psp.Spec.FSGroup.Rule))
w.Write(LEVEL_2, "Ranges:\t%s\n", groupIDRangeToString(psp.Spec.FSGroup.Ranges))
w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.FSGroup.Ranges))
w.Write(LEVEL_1, "Supplemental Groups Strategy: %s\t\n", string(psp.Spec.SupplementalGroups.Rule))
w.Write(LEVEL_2, "Ranges:\t%s\n", groupIDRangeToString(psp.Spec.SupplementalGroups.Ranges))
w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.SupplementalGroups.Ranges))
return nil
})
@ -3524,7 +3573,7 @@ func stringOrDefaultValue(s, defaultValue string) string {
return defaultValue
}
func fsTypeToString(volumes []extensions.FSType) string {
func fsTypeToString(volumes []policy.FSType) string {
strVolumes := []string{}
for _, v := range volumes {
strVolumes = append(strVolumes, string(v))
@ -3532,7 +3581,7 @@ func fsTypeToString(volumes []extensions.FSType) string {
return stringOrNone(strings.Join(strVolumes, ","))
}
func flexVolumesToString(flexVolumes []extensions.AllowedFlexVolume) string {
func flexVolumesToString(flexVolumes []policy.AllowedFlexVolume) string {
volumes := []string{}
for _, flexVolume := range flexVolumes {
volumes = append(volumes, "driver="+flexVolume.Driver)
@ -3540,7 +3589,7 @@ func flexVolumesToString(flexVolumes []extensions.AllowedFlexVolume) string {
return stringOrDefaultValue(strings.Join(volumes, ","), "<all>")
}
func hostPortRangeToString(ranges []extensions.HostPortRange) string {
func hostPortRangeToString(ranges []policy.HostPortRange) string {
formattedString := ""
if ranges != nil {
strRanges := []string{}
@ -3552,19 +3601,7 @@ func hostPortRangeToString(ranges []extensions.HostPortRange) string {
return stringOrNone(formattedString)
}
func userIDRangeToString(ranges []extensions.UserIDRange) string {
formattedString := ""
if ranges != nil {
strRanges := []string{}
for _, r := range ranges {
strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max))
}
formattedString = strings.Join(strRanges, ",")
}
return stringOrNone(formattedString)
}
func groupIDRangeToString(ranges []extensions.GroupIDRange) string {
func idRangeToString(ranges []policy.IDRange) string {
formattedString := ""
if ranges != nil {
strRanges := []string{}
@ -3794,23 +3831,19 @@ func printTaintsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerI
}
// to print taints in the sorted order
keys := make([]string, 0, len(taints))
for _, taint := range taints {
keys = append(keys, string(taint.Effect)+","+taint.Key)
}
sort.Strings(keys)
for i, key := range keys {
for _, taint := range taints {
if string(taint.Effect)+","+taint.Key == key {
if i != 0 {
w.Write(LEVEL_0, "%s", initialIndent)
w.Write(LEVEL_0, "%s", innerIndent)
}
w.Write(LEVEL_0, "%s\n", taint.ToString())
i++
}
sort.Slice(taints, func(i, j int) bool {
cmpKey := func(taint api.Taint) string {
return string(taint.Effect) + "," + taint.Key
}
return cmpKey(taints[i]) < cmpKey(taints[j])
})
for i, taint := range taints {
if i != 0 {
w.Write(LEVEL_0, "%s", initialIndent)
w.Write(LEVEL_0, "%s", innerIndent)
}
w.Write(LEVEL_0, "%s\n", taint.ToString())
}
}
@ -3829,33 +3862,26 @@ func printTolerationsMultilineWithIndent(w PrefixWriter, initialIndent, title, i
}
// to print tolerations in the sorted order
keys := make([]string, 0, len(tolerations))
for _, toleration := range tolerations {
keys = append(keys, toleration.Key)
}
sort.Strings(keys)
sort.Slice(tolerations, func(i, j int) bool {
return tolerations[i].Key < tolerations[j].Key
})
for i, key := range keys {
for _, toleration := range tolerations {
if toleration.Key == key {
if i != 0 {
w.Write(LEVEL_0, "%s", initialIndent)
w.Write(LEVEL_0, "%s", innerIndent)
}
w.Write(LEVEL_0, "%s", toleration.Key)
if len(toleration.Value) != 0 {
w.Write(LEVEL_0, "=%s", toleration.Value)
}
if len(toleration.Effect) != 0 {
w.Write(LEVEL_0, ":%s", toleration.Effect)
}
if toleration.TolerationSeconds != nil {
w.Write(LEVEL_0, " for %ds", *toleration.TolerationSeconds)
}
w.Write(LEVEL_0, "\n")
i++
}
for i, toleration := range tolerations {
if i != 0 {
w.Write(LEVEL_0, "%s", initialIndent)
w.Write(LEVEL_0, "%s", innerIndent)
}
w.Write(LEVEL_0, "%s", toleration.Key)
if len(toleration.Value) != 0 {
w.Write(LEVEL_0, "=%s", toleration.Value)
}
if len(toleration.Effect) != 0 {
w.Write(LEVEL_0, ":%s", toleration.Effect)
}
if toleration.TolerationSeconds != nil {
w.Write(LEVEL_0, " for %ds", *toleration.TolerationSeconds)
}
w.Write(LEVEL_0, "\n")
}
}
@ -3944,14 +3970,6 @@ func (list SortableVolumeDevices) Less(i, j int) bool {
return list[i].DevicePath < list[j].DevicePath
}
// TODO: get rid of this and plumb the caller correctly
func versionedExtensionsClientV1beta1(internalClient clientset.Interface) clientextensionsv1beta1.ExtensionsV1beta1Interface {
if internalClient == nil {
return &clientextensionsv1beta1.ExtensionsV1beta1Client{}
}
return clientextensionsv1beta1.New(internalClient.Extensions().RESTClient())
}
var maxAnnotationLen = 200
// printAnnotationsMultilineWithFilter prints filtered multiple annotations with a proper alignment.

View File

@ -25,14 +25,15 @@ import (
"testing"
"time"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/intstr"
versionedfake "k8s.io/client-go/kubernetes/fake"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -282,10 +283,12 @@ func getResourceList(cpu, memory string) api.ResourceList {
func TestDescribeService(t *testing.T) {
testCases := []struct {
name string
service *api.Service
expect []string
}{
{
name: "test1",
service: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
@ -323,6 +326,7 @@ func TestDescribeService(t *testing.T) {
},
},
{
name: "test2",
service: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
@ -361,18 +365,20 @@ func TestDescribeService(t *testing.T) {
},
}
for _, testCase := range testCases {
fake := fake.NewSimpleClientset(testCase.service)
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
d := ServiceDescriber{c}
out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
for _, expected := range testCase.expect {
if !strings.Contains(out, expected) {
t.Errorf("expected to find %q in output: %q", expected, out)
t.Run(testCase.name, func(t *testing.T) {
fake := fake.NewSimpleClientset(testCase.service)
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
d := ServiceDescriber{c}
out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
for _, expected := range testCase.expect {
if !strings.Contains(out, expected) {
t.Errorf("expected to find %q in output: %q", expected, out)
}
}
})
}
}
@ -452,12 +458,14 @@ func VerifyDatesInOrder(
func TestDescribeContainers(t *testing.T) {
trueVal := true
testCases := []struct {
name string
container api.Container
status api.ContainerStatus
expectedElements []string
}{
// Running state.
{
name: "test1",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@ -473,6 +481,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Waiting state.
{
name: "test2",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@ -488,6 +497,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Terminated state.
{
name: "test3",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@ -506,6 +516,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Last Terminated
{
name: "test4",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@ -529,6 +540,7 @@ func TestDescribeContainers(t *testing.T) {
},
// No state defaults to waiting.
{
name: "test5",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@ -539,6 +551,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Env
{
name: "test6",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{ConfigMapRef: &api.ConfigMapEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@ -548,6 +561,7 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: false"},
},
{
name: "test7",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", ConfigMapRef: &api.ConfigMapEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@ -557,6 +571,7 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap with prefix 'p_'\tOptional: false"},
},
{
name: "test8",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{ConfigMapRef: &api.ConfigMapEnvSource{Optional: &trueVal, LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@ -566,6 +581,7 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: true"},
},
{
name: "test9",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}, Optional: &trueVal}}}},
status: api.ContainerStatus{
Name: "test",
@ -575,6 +591,7 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret\tOptional: true"},
},
{
name: "test10",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@ -585,6 +602,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Command
{
name: "test11",
container: api.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000"}},
status: api.ContainerStatus{
Name: "test",
@ -595,6 +613,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Args
{
name: "test12",
container: api.Container{Name: "test", Image: "image", Args: []string{"time", "1000"}},
status: api.ContainerStatus{
Name: "test",
@ -605,6 +624,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Using limits.
{
name: "test13",
container: api.Container{
Name: "test",
Image: "image",
@ -625,6 +645,7 @@ func TestDescribeContainers(t *testing.T) {
},
// Using requests.
{
name: "test14",
container: api.Container{
Name: "test",
Image: "image",
@ -640,6 +661,7 @@ func TestDescribeContainers(t *testing.T) {
},
// volumeMounts read/write
{
name: "test15",
container: api.Container{
Name: "test",
Image: "image",
@ -654,6 +676,7 @@ func TestDescribeContainers(t *testing.T) {
},
// volumeMounts readonly
{
name: "test16",
container: api.Container{
Name: "test",
Image: "image",
@ -670,6 +693,7 @@ func TestDescribeContainers(t *testing.T) {
// volumeDevices
{
name: "test17",
container: api.Container{
Name: "test",
Image: "image",
@ -685,23 +709,25 @@ func TestDescribeContainers(t *testing.T) {
}
for i, testCase := range testCases {
out := new(bytes.Buffer)
pod := api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{testCase.container},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{testCase.status},
},
}
writer := NewPrefixWriter(out)
describeContainers("Containers", pod.Spec.Containers, pod.Status.ContainerStatuses, EnvValueRetriever(&pod), writer, "")
output := out.String()
for _, expected := range testCase.expectedElements {
if !strings.Contains(output, expected) {
t.Errorf("Test case %d: expected to find %q in output: %q", i, expected, output)
t.Run(testCase.name, func(t *testing.T) {
out := new(bytes.Buffer)
pod := api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{testCase.container},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{testCase.status},
},
}
}
writer := NewPrefixWriter(out)
describeContainers("Containers", pod.Spec.Containers, pod.Status.ContainerStatuses, EnvValueRetriever(&pod), writer, "")
output := out.String()
for _, expected := range testCase.expectedElements {
if !strings.Contains(output, expected) {
t.Errorf("Test case %d: expected to find %q in output: %q", i, expected, output)
}
}
})
}
}
@ -787,10 +813,12 @@ func TestDefaultDescribers(t *testing.T) {
func TestGetPodsTotalRequests(t *testing.T) {
testCases := []struct {
name string
pods *api.PodList
expectedReqs map[api.ResourceName]resource.Quantity
}{
{
name: "test1",
pods: &api.PodList{
Items: []api.Pod{
{
@ -852,10 +880,12 @@ func TestGetPodsTotalRequests(t *testing.T) {
}
for _, testCase := range testCases {
reqs, _ := getPodsTotalRequestsAndLimits(testCase.pods)
if !apiequality.Semantic.DeepEqual(reqs, testCase.expectedReqs) {
t.Errorf("Expected %v, got %v", testCase.expectedReqs, reqs)
}
t.Run(testCase.name, func(t *testing.T) {
reqs, _ := getPodsTotalRequestsAndLimits(testCase.pods)
if !apiequality.Semantic.DeepEqual(reqs, testCase.expectedReqs) {
t.Errorf("Expected %v, got %v", testCase.expectedReqs, reqs)
}
})
}
}
@ -863,12 +893,14 @@ func TestPersistentVolumeDescriber(t *testing.T) {
block := api.PersistentVolumeBlock
file := api.PersistentVolumeFilesystem
testCases := []struct {
name string
plugin string
pv *api.PersistentVolume
expectedElements []string
unexpectedElements []string
}{
{
name: "test0",
plugin: "hostpath",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -881,6 +913,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test1",
plugin: "gce",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -894,6 +927,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
expectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test2",
plugin: "ebs",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -906,6 +940,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test3",
plugin: "nfs",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -918,6 +953,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test4",
plugin: "iscsi",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -931,6 +967,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
expectedElements: []string{"VolumeMode", "Block"},
},
{
name: "test5",
plugin: "gluster",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -943,6 +980,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test6",
plugin: "rbd",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -955,6 +993,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test7",
plugin: "quobyte",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -967,18 +1006,20 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test8",
plugin: "cinder",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
Cinder: &api.CinderVolumeSource{},
Cinder: &api.CinderPersistentVolumeSource{},
},
},
},
unexpectedElements: []string{"VolumeMode", "Filesystem"},
},
{
name: "test9",
plugin: "fc",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -992,6 +1033,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
expectedElements: []string{"VolumeMode", "Block"},
},
{
name: "test10",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -1005,6 +1047,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"Required Terms", "Term "},
},
{
name: "test11",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -1019,6 +1062,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"Required Terms", "Term "},
},
{
name: "test12",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -1035,6 +1079,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
unexpectedElements: []string{"Term "},
},
{
name: "test13",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -1059,6 +1104,7 @@ func TestPersistentVolumeDescriber(t *testing.T) {
expectedElements: []string{"Node Affinity", "Required Terms", "Term 0", "Term 1"},
},
{
name: "test14",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
@ -1094,25 +1140,27 @@ func TestPersistentVolumeDescriber(t *testing.T) {
}
for _, test := range testCases {
fake := fake.NewSimpleClientset(test.pv)
c := PersistentVolumeDescriber{fake}
str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("Unexpected error for test %s: %v", test.plugin, err)
}
if str == "" {
t.Errorf("Unexpected empty string for test %s. Expected PV Describer output", test.plugin)
}
for _, expected := range test.expectedElements {
if !strings.Contains(str, expected) {
t.Errorf("expected to find %q in output: %q", expected, str)
t.Run(test.name, func(t *testing.T) {
fake := fake.NewSimpleClientset(test.pv)
c := PersistentVolumeDescriber{fake}
str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("Unexpected error for test %s: %v", test.plugin, err)
}
}
for _, unexpected := range test.unexpectedElements {
if strings.Contains(str, unexpected) {
t.Errorf("unexpected to find %q in output: %q", unexpected, str)
if str == "" {
t.Errorf("Unexpected empty string for test %s. Expected PV Describer output", test.plugin)
}
}
for _, expected := range test.expectedElements {
if !strings.Contains(str, expected) {
t.Errorf("expected to find %q in output: %q", expected, str)
}
}
for _, unexpected := range test.unexpectedElements {
if strings.Contains(str, unexpected) {
t.Errorf("unexpected to find %q in output: %q", unexpected, str)
}
}
})
}
}
@ -1120,6 +1168,7 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) {
block := api.PersistentVolumeBlock
file := api.PersistentVolumeFilesystem
goldClassName := "gold"
now := time.Now()
testCases := []struct {
name string
pvc *api.PersistentVolumeClaim
@ -1170,39 +1219,138 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) {
},
expectedElements: []string{"VolumeMode", "Block"},
},
// Tests for Status.Condition.
{
name: "condition-type",
pvc: &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume4",
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Conditions: []api.PersistentVolumeClaimCondition{
{Type: api.PersistentVolumeClaimResizing},
},
},
},
expectedElements: []string{"Conditions", "Type", "Resizing"},
},
{
name: "condition-status",
pvc: &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume5",
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Conditions: []api.PersistentVolumeClaimCondition{
{Status: api.ConditionTrue},
},
},
},
expectedElements: []string{"Conditions", "Status", "True"},
},
{
name: "condition-last-probe-time",
pvc: &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume6",
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Conditions: []api.PersistentVolumeClaimCondition{
{LastProbeTime: metav1.Time{Time: now}},
},
},
},
expectedElements: []string{"Conditions", "LastProbeTime", now.Format(time.RFC1123Z)},
},
{
name: "condition-last-transition-time",
pvc: &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume7",
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Conditions: []api.PersistentVolumeClaimCondition{
{LastTransitionTime: metav1.Time{Time: now}},
},
},
},
expectedElements: []string{"Conditions", "LastTransitionTime", now.Format(time.RFC1123Z)},
},
{
name: "condition-reason",
pvc: &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume8",
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Conditions: []api.PersistentVolumeClaimCondition{
{Reason: "OfflineResize"},
},
},
},
expectedElements: []string{"Conditions", "Reason", "OfflineResize"},
},
{
name: "condition-message",
pvc: &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume9",
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Conditions: []api.PersistentVolumeClaimCondition{
{Message: "User request resize"},
},
},
},
expectedElements: []string{"Conditions", "Message", "User request resize"},
},
}
for _, test := range testCases {
fake := fake.NewSimpleClientset(test.pvc)
c := PersistentVolumeClaimDescriber{fake}
str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("Unexpected error for test %s: %v", test.name, err)
}
if str == "" {
t.Errorf("Unexpected empty string for test %s. Expected PVC Describer output", test.name)
}
for _, expected := range test.expectedElements {
if !strings.Contains(str, expected) {
t.Errorf("expected to find %q in output: %q", expected, str)
t.Run(test.name, func(t *testing.T) {
fake := fake.NewSimpleClientset(test.pvc)
c := PersistentVolumeClaimDescriber{fake}
str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("Unexpected error for test %s: %v", test.name, err)
}
}
for _, unexpected := range test.unexpectedElements {
if strings.Contains(str, unexpected) {
t.Errorf("unexpected to find %q in output: %q", unexpected, str)
if str == "" {
t.Errorf("Unexpected empty string for test %s. Expected PVC Describer output", test.name)
}
}
for _, expected := range test.expectedElements {
if !strings.Contains(str, expected) {
t.Errorf("expected to find %q in output: %q", expected, str)
}
}
for _, unexpected := range test.unexpectedElements {
if strings.Contains(str, unexpected) {
t.Errorf("unexpected to find %q in output: %q", unexpected, str)
}
}
})
}
}
func TestDescribeDeployment(t *testing.T) {
fake := fake.NewSimpleClientset()
versionedFake := versionedfake.NewSimpleClientset(&v1beta1.Deployment{
versionedFake := versionedfake.NewSimpleClientset(&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: v1beta1.DeploymentSpec{
Spec: appsv1.DeploymentSpec{
Replicas: utilpointer.Int32Ptr(1),
Selector: &metav1.LabelSelector{},
Template: v1.PodTemplateSpec{
@ -1214,7 +1362,7 @@ func TestDescribeDeployment(t *testing.T) {
},
},
})
d := DeploymentDescriber{fake, versionedFake.ExtensionsV1beta1()}
d := DeploymentDescriber{fake, versionedFake}
out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error: %v", err)
@ -1781,20 +1929,22 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
}
for _, test := range tests {
test.hpa.ObjectMeta = metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
}
fake := fake.NewSimpleClientset(&test.hpa)
desc := HorizontalPodAutoscalerDescriber{fake}
str, err := desc.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("Unexpected error for test %s: %v", test.name, err)
}
if str == "" {
t.Errorf("Unexpected empty string for test %s. Expected HPA Describer output", test.name)
}
t.Logf("Description for %q:\n%s", test.name, str)
t.Run(test.name, func(t *testing.T) {
test.hpa.ObjectMeta = metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
}
fake := fake.NewSimpleClientset(&test.hpa)
desc := HorizontalPodAutoscalerDescriber{fake}
str, err := desc.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("Unexpected error for test %s: %v", test.name, err)
}
if str == "" {
t.Errorf("Unexpected empty string for test %s. Expected HPA Describer output", test.name)
}
t.Logf("Description for %q:\n%s", test.name, str)
})
}
}
@ -1827,16 +1977,16 @@ func TestDescribeEvents(t *testing.T) {
},
"DeploymentDescriber": &DeploymentDescriber{
fake.NewSimpleClientset(events),
versionedfake.NewSimpleClientset(&v1beta1.Deployment{
versionedfake.NewSimpleClientset(&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: v1beta1.DeploymentSpec{
Spec: appsv1.DeploymentSpec{
Replicas: utilpointer.Int32Ptr(1),
Selector: &metav1.LabelSelector{},
},
}).ExtensionsV1beta1(),
}),
},
"EndpointsDescriber": &EndpointsDescriber{
fake.NewSimpleClientset(&api.Endpoints{
@ -1924,27 +2074,29 @@ func TestDescribeEvents(t *testing.T) {
}
for name, d := range m {
out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error for %q: %v", name, err)
}
if !strings.Contains(out, "bar") {
t.Errorf("unexpected out for %q: %s", name, out)
}
if !strings.Contains(out, "Events:") {
t.Errorf("events not found for %q when ShowEvents=true: %s", name, out)
}
t.Run(name, func(t *testing.T) {
out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error for %q: %v", name, err)
}
if !strings.Contains(out, "bar") {
t.Errorf("unexpected out for %q: %s", name, out)
}
if !strings.Contains(out, "Events:") {
t.Errorf("events not found for %q when ShowEvents=true: %s", name, out)
}
out, err = d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: false})
if err != nil {
t.Errorf("unexpected error for %q: %s", name, err)
}
if !strings.Contains(out, "bar") {
t.Errorf("unexpected out for %q: %s", name, out)
}
if strings.Contains(out, "Events:") {
t.Errorf("events found for %q when ShowEvents=false: %s", name, out)
}
out, err = d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: false})
if err != nil {
t.Errorf("unexpected error for %q: %s", name, err)
}
if !strings.Contains(out, "bar") {
t.Errorf("unexpected out for %q: %s", name, out)
}
if strings.Contains(out, "Events:") {
t.Errorf("events found for %q when ShowEvents=false: %s", name, out)
}
})
}
}
@ -1972,13 +2124,15 @@ func TestPrintLabelsMultiline(t *testing.T) {
},
}
for i, testCase := range testCases {
out := new(bytes.Buffer)
writer := NewPrefixWriter(out)
printAnnotationsMultiline(writer, "Annotations", testCase.annotations)
output := out.String()
if output != testCase.expectPrint {
t.Errorf("Test case %d: expected to find %q in output: %q", i, testCase.expectPrint, output)
}
t.Run(testCase.expectPrint, func(t *testing.T) {
out := new(bytes.Buffer)
writer := NewPrefixWriter(out)
printAnnotationsMultiline(writer, "Annotations", testCase.annotations)
output := out.String()
if output != testCase.expectPrint {
t.Errorf("Test case %d: expected to find %q in output: %q", i, testCase.expectPrint, output)
}
})
}
}
@ -2089,22 +2243,22 @@ func TestDescribePodSecurityPolicy(t *testing.T) {
"Supplemental Groups Strategy: RunAsAny",
}
fake := fake.NewSimpleClientset(&extensions.PodSecurityPolicy{
fake := fake.NewSimpleClientset(&policy.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "mypsp",
},
Spec: extensions.PodSecurityPolicySpec{
SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyRunAsAny,
Spec: policy.PodSecurityPolicySpec{
SELinux: policy.SELinuxStrategyOptions{
Rule: policy.SELinuxStrategyRunAsAny,
},
RunAsUser: extensions.RunAsUserStrategyOptions{
Rule: extensions.RunAsUserStrategyRunAsAny,
RunAsUser: policy.RunAsUserStrategyOptions{
Rule: policy.RunAsUserStrategyRunAsAny,
},
FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyRunAsAny,
FSGroup: policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyRunAsAny,
},
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
Rule: extensions.SupplementalGroupsStrategyRunAsAny,
SupplementalGroups: policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyRunAsAny,
},
},
})
@ -2177,10 +2331,17 @@ Spec:
Allowing ingress traffic:
To Port: 80/TCP
To Port: 82/TCP
From PodSelector: id=app2,id2=app3
From NamespaceSelector: id=app2,id2=app3
From NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
From IPBlock:
From:
NamespaceSelector: id=ns1,id2=ns2
PodSelector: id=pod1,id2=pod2
From:
PodSelector: id=app2,id2=app3
From:
NamespaceSelector: id=app2,id2=app3
From:
NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
From:
IPBlock:
CIDR: 192.168.0.0/16
Except: 192.168.3.0/24, 192.168.4.0/24
----------
@ -2189,10 +2350,17 @@ Spec:
Allowing egress traffic:
To Port: 80/TCP
To Port: 82/TCP
To PodSelector: id=app2,id2=app3
To NamespaceSelector: id=app2,id2=app3
To NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
To IPBlock:
To:
NamespaceSelector: id=ns1,id2=ns2
PodSelector: id=pod1,id2=pod2
To:
PodSelector: id=app2,id2=app3
To:
NamespaceSelector: id=app2,id2=app3
To:
NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
To:
IPBlock:
CIDR: 192.168.0.0/16
Except: 192.168.3.0/24, 192.168.4.0/24
----------
@ -2229,6 +2397,20 @@ Spec:
{Port: &port82, Protocol: &protoTCP},
},
From: []networking.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"id": "pod1",
"id2": "pod2",
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"id": "ns1",
"id2": "ns2",
},
},
},
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
@ -2273,6 +2455,20 @@ Spec:
{Port: &port82, Protocol: &protoTCP},
},
To: []networking.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"id": "pod1",
"id2": "pod2",
},
},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"id": "ns1",
"id2": "ns2",
},
},
},
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
@ -2386,6 +2582,46 @@ func TestDescribeNode(t *testing.T) {
}
func TestDescribeStatefulSet(t *testing.T) {
fake := fake.NewSimpleClientset(&apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: apps.StatefulSetSpec{
Replicas: 1,
Selector: &metav1.LabelSelector{},
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
Containers: []api.Container{
{Image: "mytest-image:latest"},
},
},
},
UpdateStrategy: apps.StatefulSetUpdateStrategy{
Type: apps.RollingUpdateStatefulSetStrategyType,
RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{
Partition: 2,
},
},
},
})
d := StatefulSetDescriber{fake}
out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedOutputs := []string{
"bar", "foo", "Containers:", "mytest-image:latest", "Update Strategy", "RollingUpdate", "Partition",
}
for _, o := range expectedOutputs {
if !strings.Contains(out, o) {
t.Errorf("unexpected out: %s", out)
break
}
}
}
// boolPtr returns a pointer to a bool
func boolPtr(b bool) *bool {
o := b

View File

@ -40,6 +40,7 @@ import (
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api/events"
@ -220,6 +221,7 @@ func AddHandlers(h printers.PrintHandler) {
{Name: "Roles", Type: "string", Description: "The roles of the node"},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "Version", Type: "string", Description: apiv1.NodeSystemInfo{}.SwaggerDoc()["kubeletVersion"]},
{Name: "Internal-IP", Type: "string", Priority: 1, Description: apiv1.NodeStatus{}.SwaggerDoc()["addresses"]},
{Name: "External-IP", Type: "string", Priority: 1, Description: apiv1.NodeStatus{}.SwaggerDoc()["addresses"]},
{Name: "OS-Image", Type: "string", Priority: 1, Description: apiv1.NodeSystemInfo{}.SwaggerDoc()["osImage"]},
{Name: "Kernel-Version", Type: "string", Priority: 1, Description: apiv1.NodeSystemInfo{}.SwaggerDoc()["kernelVersion"]},
@ -286,7 +288,7 @@ func AddHandlers(h printers.PrintHandler) {
persistentVolumeClaimColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Status", Type: "string", Description: apiv1.PersistentVolumeClaimStatus{}.SwaggerDoc()["phase"]},
{Name: "Volume", Type: "string", Description: apiv1.PersistentVolumeSpec{}.SwaggerDoc()["volumeName"]},
{Name: "Volume", Type: "string", Description: apiv1.PersistentVolumeClaimSpec{}.SwaggerDoc()["volumeName"]},
{Name: "Capacity", Type: "string", Description: apiv1.PersistentVolumeClaimStatus{}.SwaggerDoc()["capacity"]},
{Name: "Access Modes", Type: "string", Description: apiv1.PersistentVolumeClaimStatus{}.SwaggerDoc()["accessModes"]},
{Name: "StorageClass", Type: "string", Description: "StorageClass of the pvc"},
@ -340,7 +342,7 @@ func AddHandlers(h printers.PrintHandler) {
podSecurityPolicyColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Data", Type: "string", Description: extensionsv1beta1.PodSecurityPolicySpec{}.SwaggerDoc()["privileged"]},
{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"]},
@ -501,7 +503,8 @@ func translateTimestamp(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.ShortHumanDuration(time.Now().Sub(timestamp.Time))
return duration.ShortHumanDuration(time.Since(timestamp.Time))
}
var (
@ -572,6 +575,7 @@ func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1beta1.TableR
}
if !initializing {
restarts = 0
hasRunning := false
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
container := pod.Status.ContainerStatuses[i]
@ -587,9 +591,15 @@ func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1beta1.TableR
reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
}
} else if container.Ready && container.State.Running != nil {
hasRunning = true
readyContainers++
}
}
// change pod status back to "Running" if there is at least one container still reporting as "Running" status
if reason == "Completed" && hasRunning {
reason = "Running"
}
}
if pod.DeletionTimestamp != nil && pod.Status.Reason == node.NodeUnreachablePodReason {
@ -598,7 +608,7 @@ 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, restarts, translateTimestamp(pod.CreationTimestamp))
row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, int64(restarts), translateTimestamp(pod.CreationTimestamp))
if options.Wide {
nodeName := pod.Spec.NodeName
@ -658,7 +668,7 @@ func printPodDisruptionBudget(obj *policy.PodDisruptionBudget, options printers.
maxUnavailable = "N/A"
}
row.Cells = append(row.Cells, obj.Name, minAvailable, maxUnavailable, obj.Status.PodDisruptionsAllowed, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, minAvailable, maxUnavailable, int64(obj.Status.PodDisruptionsAllowed), translateTimestamp(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -684,7 +694,7 @@ func printReplicationController(obj *api.ReplicationController, options printers
currentReplicas := obj.Status.Replicas
readyReplicas := obj.Status.ReadyReplicas
row.Cells = append(row.Cells, obj.Name, desiredReplicas, currentReplicas, readyReplicas, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestamp(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))
@ -713,7 +723,7 @@ func printReplicaSet(obj *extensions.ReplicaSet, options printers.PrintOptions)
currentReplicas := obj.Status.Replicas
readyReplicas := obj.Status.ReadyReplicas
row.Cells = append(row.Cells, obj.Name, desiredReplicas, currentReplicas, readyReplicas, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestamp(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))
@ -745,7 +755,7 @@ func printJob(obj *batch.Job, options printers.PrintOptions) ([]metav1beta1.Tabl
completions = "<none>"
}
row.Cells = append(row.Cells, obj.Name, completions, obj.Status.Succeeded, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, completions, int64(obj.Status.Succeeded), translateTimestamp(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))
@ -775,7 +785,7 @@ func printCronJob(obj *batch.CronJob, options printers.PrintOptions) ([]metav1be
lastScheduleTime = translateTimestamp(*obj.Status.LastScheduleTime)
}
row.Cells = append(row.Cells, obj.Name, obj.Spec.Schedule, printBoolPtr(obj.Spec.Suspend), 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, translateTimestamp(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))
@ -962,7 +972,7 @@ func printStatefulSet(obj *apps.StatefulSet, options printers.PrintOptions) ([]m
desiredReplicas := obj.Spec.Replicas
currentReplicas := obj.Status.Replicas
createTime := translateTimestamp(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, desiredReplicas, currentReplicas, createTime)
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), createTime)
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images)
@ -993,7 +1003,7 @@ func printDaemonSet(obj *extensions.DaemonSet, options printers.PrintOptions) ([
numberUpdated := obj.Status.UpdatedNumberScheduled
numberAvailable := obj.Status.NumberAvailable
row.Cells = append(row.Cells, obj.Name, desiredScheduled, currentScheduled, numberReady, numberUpdated, 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), translateTimestamp(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))
@ -1057,7 +1067,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, obj.Type, len(obj.Data), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(obj.Type), int64(len(obj.Data)), translateTimestamp(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1077,7 +1087,7 @@ func printServiceAccount(obj *api.ServiceAccount, options printers.PrintOptions)
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, len(obj.Secrets), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Secrets)), translateTimestamp(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1138,7 +1148,7 @@ func printNode(obj *api.Node, options printers.PrintOptions) ([]metav1beta1.Tabl
if crVersion == "" {
crVersion = "<unknown>"
}
row.Cells = append(row.Cells, getNodeExternalIP(obj), osImage, kernelVersion, crVersion)
row.Cells = append(row.Cells, getNodeInternalIP(obj), getNodeExternalIP(obj), osImage, kernelVersion, crVersion)
}
return []metav1beta1.TableRow{row}, nil
@ -1155,6 +1165,17 @@ func getNodeExternalIP(node *api.Node) string {
return "<none>"
}
// Returns the internal IP of the node or "<none>" if none is found.
func getNodeInternalIP(node *api.Node) string {
for _, address := range node.Status.Addresses {
if address.Type == api.NodeInternalIP {
return address.Address
}
}
return "<none>"
}
// findNodeRoles returns the roles of a given node.
// The roles are determined by looking for:
// * a node-role.kubernetes.io/<role>="" label
@ -1211,7 +1232,7 @@ func printPersistentVolume(obj *api.PersistentVolume, options printers.PrintOpti
}
row.Cells = append(row.Cells, obj.Name, aSize, modesStr, reclaimPolicyStr,
phase, claimRefUID, helper.GetPersistentVolumeClass(obj),
string(phase), claimRefUID, helper.GetPersistentVolumeClass(obj),
obj.Status.Reason,
translateTimestamp(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
@ -1248,7 +1269,7 @@ func printPersistentVolumeClaim(obj *api.PersistentVolumeClaim, options printers
capacity = storage.String()
}
row.Cells = append(row.Cells, obj.Name, 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), translateTimestamp(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1278,7 +1299,7 @@ func printEvent(obj *api.Event, options printers.PrintOptions) ([]metav1beta1.Ta
LastTimestamp = translateTimestamp(obj.LastTimestamp)
}
row.Cells = append(row.Cells, LastTimestamp, FirstTimestamp,
obj.Count, obj.Name, obj.InvolvedObject.Kind,
int64(obj.Count), obj.Name, obj.InvolvedObject.Kind,
obj.InvolvedObject.FieldPath, obj.Type, obj.Reason,
formatEventSource(obj.Source), obj.Message)
@ -1461,7 +1482,7 @@ func printDeployment(obj *extensions.Deployment, options printers.PrintOptions)
// this shouldn't happen if LabelSelector passed validation
return nil, err
}
row.Cells = append(row.Cells, obj.Name, desiredReplicas, currentReplicas, updatedReplicas, availableReplicas, age)
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(updatedReplicas), int64(availableReplicas), age)
if options.Wide {
containers, images := layoutContainerCells(containers)
row.Cells = append(row.Cells, containers, images, selector.String())
@ -1570,7 +1591,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, maxPods, currentReplicas, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, reference, metrics, minPods, int64(maxPods), int64(currentReplicas), translateTimestamp(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1590,7 +1611,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, len(obj.Data), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Data)), translateTimestamp(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@ -1606,17 +1627,28 @@ func printConfigMapList(list *api.ConfigMapList, options printers.PrintOptions)
return rows, nil
}
func printPodSecurityPolicy(obj *extensions.PodSecurityPolicy, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printPodSecurityPolicy(obj *policy.PodSecurityPolicy, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, obj.Spec.Privileged,
obj.Spec.AllowedCapabilities, obj.Spec.SELinux.Rule,
obj.Spec.RunAsUser.Rule, obj.Spec.FSGroup.Rule, obj.Spec.SupplementalGroups.Rule, obj.Spec.ReadOnlyRootFilesystem, obj.Spec.Volumes)
capabilities := make([]string, len(obj.Spec.AllowedCapabilities))
for i, c := range obj.Spec.AllowedCapabilities {
capabilities[i] = string(c)
}
volumes := make([]string, len(obj.Spec.Volumes))
for i, v := range obj.Spec.Volumes {
volumes[i] = string(v)
}
row.Cells = append(row.Cells, obj.Name, fmt.Sprintf("%v", obj.Spec.Privileged),
strings.Join(capabilities, ","), string(obj.Spec.SELinux.Rule),
string(obj.Spec.RunAsUser.Rule), string(obj.Spec.FSGroup.Rule),
string(obj.Spec.SupplementalGroups.Rule), obj.Spec.ReadOnlyRootFilesystem,
strings.Join(volumes, ","))
return []metav1beta1.TableRow{row}, nil
}
func printPodSecurityPolicyList(list *extensions.PodSecurityPolicyList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
func printPodSecurityPolicyList(list *policy.PodSecurityPolicyList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printPodSecurityPolicy(&list.Items[i], options)
@ -1736,7 +1768,12 @@ func printControllerRevision(obj *apps.ControllerRevision, options printers.Prin
controllerName := "<none>"
if controllerRef != nil {
withKind := true
controllerName = printers.FormatResourceName(controllerRef.Kind, controllerRef.Name, withKind)
gv, err := schema.ParseGroupVersion(controllerRef.APIVersion)
if err != nil {
return nil, err
}
gvk := gv.WithKind(controllerRef.Kind)
controllerName = printers.FormatResourceName(gvk.GroupKind(), controllerRef.Name, withKind)
}
revision := obj.Revision
age := translateTimestamp(obj.CreationTimestamp)

File diff suppressed because it is too large Load Diff

View File

@ -1,109 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"k8s.io/apimachinery/pkg/runtime"
"github.com/ghodss/yaml"
)
// JSONPrinter is an implementation of ResourcePrinter which outputs an object as JSON.
type JSONPrinter struct {
}
func (p *JSONPrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer.
func (p *JSONPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
switch obj := obj.(type) {
case *runtime.Unknown:
var buf bytes.Buffer
err := json.Indent(&buf, obj.Raw, "", " ")
if err != nil {
return err
}
buf.WriteRune('\n')
_, err = buf.WriteTo(w)
return err
}
data, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return err
}
data = append(data, '\n')
_, err = w.Write(data)
return err
}
// TODO: implement HandledResources()
func (p *JSONPrinter) HandledResources() []string {
return []string{}
}
func (p *JSONPrinter) IsGeneric() bool {
return true
}
// YAMLPrinter is an implementation of ResourcePrinter which outputs an object as YAML.
// The input object is assumed to be in the internal version of an API and is converted
// to the given version first.
type YAMLPrinter struct {
version string
converter runtime.ObjectConvertor
}
func (p *YAMLPrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj prints the data as YAML.
func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
switch obj := obj.(type) {
case *runtime.Unknown:
data, err := yaml.JSONToYAML(obj.Raw)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
output, err := yaml.Marshal(obj)
if err != nil {
return err
}
_, err = fmt.Fprint(w, string(output))
return err
}
// TODO: implement HandledResources()
func (p *YAMLPrinter) HandledResources() []string {
return []string{}
}
func (p *YAMLPrinter) IsGeneric() bool {
return true
}

View File

@ -1,161 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"encoding/json"
"fmt"
"io"
"reflect"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/jsonpath"
)
// exists returns true if it would be possible to call the index function
// with these arguments.
//
// TODO: how to document this for users?
//
// index returns the result of indexing its first argument by the following
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
// indexed item must be a map, slice, or array.
func exists(item interface{}, indices ...interface{}) bool {
v := reflect.ValueOf(item)
for _, i := range indices {
index := reflect.ValueOf(i)
var isNil bool
if v, isNil = indirect(v); isNil {
return false
}
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
var x int64
switch index.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x = index.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
x = int64(index.Uint())
default:
return false
}
if x < 0 || x >= int64(v.Len()) {
return false
}
v = v.Index(int(x))
case reflect.Map:
if !index.IsValid() {
index = reflect.Zero(v.Type().Key())
}
if !index.Type().AssignableTo(v.Type().Key()) {
return false
}
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {
v = reflect.Zero(v.Type().Elem())
}
default:
return false
}
}
if _, isNil := indirect(v); isNil {
return false
}
return true
}
// stolen from text/template
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
// We indirect through pointers and empty interfaces (only) because
// non-empty interfaces have methods we might need.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}
// JSONPathPrinter is an implementation of ResourcePrinter which formats data with jsonpath expression.
type JSONPathPrinter struct {
rawTemplate string
*jsonpath.JSONPath
}
func NewJSONPathPrinter(tmpl string) (*JSONPathPrinter, error) {
j := jsonpath.New("out")
if err := j.Parse(tmpl); err != nil {
return nil, err
}
return &JSONPathPrinter{tmpl, j}, nil
}
func (j *JSONPathPrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj formats the obj with the JSONPath Template.
func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
var queryObj interface{} = obj
if meta.IsListType(obj) {
data, err := json.Marshal(obj)
if err != nil {
return err
}
queryObj = map[string]interface{}{}
if err := json.Unmarshal(data, &queryObj); err != nil {
return err
}
}
if unknown, ok := obj.(*runtime.Unknown); ok {
data, err := json.Marshal(unknown)
if err != nil {
return err
}
queryObj = map[string]interface{}{}
if err := json.Unmarshal(data, &queryObj); err != nil {
return err
}
}
if unstructured, ok := obj.(runtime.Unstructured); ok {
queryObj = unstructured.UnstructuredContent()
}
if err := j.JSONPath.Execute(w, queryObj); err != nil {
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err)
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", j.rawTemplate)
fmt.Fprintf(w, "\tobject given to jsonpath engine was:\n\t\t%#v\n\n", queryObj)
return fmt.Errorf("error executing jsonpath %q: %v\n", j.rawTemplate, err)
}
return nil
}
// TODO: implement HandledResources()
func (p *JSONPathPrinter) HandledResources() []string {
return []string{}
}
func (p *JSONPathPrinter) IsGeneric() bool {
return true
}

View File

@ -1,118 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"fmt"
"io"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
// NamePrinter is an implementation of ResourcePrinter which outputs "resource/name" pair of an object.
type NamePrinter struct {
Decoders []runtime.Decoder
Typer runtime.ObjectTyper
}
func (p *NamePrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj is an implementation of ResourcePrinter.PrintObj which decodes the object
// and print "resource/name" pair. If the object is a List, print all items in it.
func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
if meta.IsListType(obj) {
items, err := meta.ExtractList(obj)
if err != nil {
return err
}
if errs := runtime.DecodeList(items, p.Decoders...); len(errs) > 0 {
return utilerrors.NewAggregate(errs)
}
for _, obj := range items {
if err := p.PrintObj(obj, w); err != nil {
return err
}
}
return nil
}
name := "<unknown>"
if acc, err := meta.Accessor(obj); err == nil {
if n := acc.GetName(); len(n) > 0 {
name = n
}
}
return printObj(w, name, GetObjectGroupKind(obj, p.Typer))
}
func GetObjectGroupKind(obj runtime.Object, typer runtime.ObjectTyper) schema.GroupKind {
if obj == nil {
return schema.GroupKind{Kind: "<unknown>"}
}
groupVersionKind := obj.GetObjectKind().GroupVersionKind()
if len(groupVersionKind.Kind) > 0 {
return groupVersionKind.GroupKind()
}
if gvks, _, err := typer.ObjectKinds(obj); err == nil {
for _, gvk := range gvks {
if len(gvk.Kind) == 0 {
continue
}
return gvk.GroupKind()
}
}
if uns, ok := obj.(*unstructured.Unstructured); ok {
if len(uns.GroupVersionKind().Kind) > 0 {
return uns.GroupVersionKind().GroupKind()
}
}
return schema.GroupKind{Kind: "<unknown>"}
}
func printObj(w io.Writer, name string, groupKind schema.GroupKind) error {
if len(groupKind.Kind) == 0 {
return fmt.Errorf("missing kind for resource with name %v", name)
}
if len(groupKind.Group) == 0 {
fmt.Fprintf(w, "%s/%s\n", strings.ToLower(groupKind.Kind), name)
return nil
}
fmt.Fprintf(w, "%s.%s/%s\n", strings.ToLower(groupKind.Kind), groupKind.Group, name)
return nil
}
// TODO: implement HandledResources()
func (p *NamePrinter) HandledResources() []string {
return []string{}
}
func (p *NamePrinter) IsGeneric() bool {
return true
}

View File

@ -1,126 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"fmt"
"io/ioutil"
"os"
"k8s.io/apimachinery/pkg/runtime"
)
// GetStandardPrinter takes a format type, an optional format argument. It will return
// a printer or an error. The printer is agnostic to schema versions, so you must
// send arguments to PrintObj in the version you wish them to be shown using a
// VersionedPrinter (typically when generic is true).
func GetStandardPrinter(typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options PrintOptions) (ResourcePrinter, error) {
format, formatArgument, allowMissingTemplateKeys := options.OutputFormatType, options.OutputFormatArgument, options.AllowMissingKeys
var printer ResourcePrinter
switch format {
case "json":
printer = &JSONPrinter{}
case "yaml":
printer = &YAMLPrinter{}
case "name":
printer = &NamePrinter{
Typer: typer,
Decoders: decoders,
}
case "template", "go-template":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("template format specified but no template given")
}
templatePrinter, err := NewTemplatePrinter([]byte(formatArgument))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", formatArgument, err)
}
templatePrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = templatePrinter
case "templatefile", "go-template-file":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("templatefile format specified but no template file given")
}
data, err := ioutil.ReadFile(formatArgument)
if err != nil {
return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
}
templatePrinter, err := NewTemplatePrinter(data)
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
}
templatePrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = templatePrinter
case "jsonpath":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("jsonpath template format specified but no template given")
}
jsonpathPrinter, err := NewJSONPathPrinter(formatArgument)
if err != nil {
return nil, fmt.Errorf("error parsing jsonpath %s, %v\n", formatArgument, err)
}
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter
case "jsonpath-file":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("jsonpath file format specified but no template file given")
}
data, err := ioutil.ReadFile(formatArgument)
if err != nil {
return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
}
jsonpathPrinter, err := NewJSONPathPrinter(string(data))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
}
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter
case "custom-columns":
var err error
if printer, err = NewCustomColumnsPrinterFromSpec(formatArgument, decoders[0], options.NoHeaders); err != nil {
return nil, err
}
case "custom-columns-file":
file, err := os.Open(formatArgument)
if err != nil {
return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
}
defer file.Close()
if printer, err = NewCustomColumnsPrinterFromTemplate(file, decoders[0]); err != nil {
return nil, err
}
case "wide":
fallthrough
case "":
printer = NewHumanReadablePrinter(encoder, decoders[0], options)
default:
return nil, fmt.Errorf("output format %q not recognized", format)
}
return printer, nil
}

View File

@ -13,7 +13,6 @@ go_library(
"//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",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)

View File

@ -17,9 +17,10 @@ limitations under the License.
package storage
import (
"context"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/printers"
)
@ -27,6 +28,6 @@ type TableConvertor struct {
printers.TablePrinter
}
func (c TableConvertor) ConvertToTable(ctx genericapirequest.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
func (c TableConvertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
return c.TablePrinter.PrintTable(obj, printers.PrintOptions{Wide: true})
}

View File

@ -1,114 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"encoding/json"
"fmt"
"io"
"text/template"
"k8s.io/apimachinery/pkg/runtime"
)
// TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type TemplatePrinter struct {
rawTemplate string
template *template.Template
}
func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) {
t, err := template.New("output").
Funcs(template.FuncMap{"exists": exists}).
Parse(string(tmpl))
if err != nil {
return nil, err
}
return &TemplatePrinter{
rawTemplate: string(tmpl),
template: t,
}, nil
}
// AllowMissingKeys tells the template engine if missing keys are allowed.
func (p *TemplatePrinter) AllowMissingKeys(allow bool) {
if allow {
p.template.Option("missingkey=default")
} else {
p.template.Option("missingkey=error")
}
}
func (p *TemplatePrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj formats the obj with the Go Template.
func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
var data []byte
var err error
data, err = json.Marshal(obj)
if err != nil {
return err
}
out := map[string]interface{}{}
if err := json.Unmarshal(data, &out); err != nil {
return err
}
if err = p.safeExecute(w, out); err != nil {
// It is way easier to debug this stuff when it shows up in
// stdout instead of just stdin. So in addition to returning
// a nice error, also print useful stuff with the writer.
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err)
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", p.rawTemplate)
fmt.Fprintf(w, "\traw data was:\n\t\t%v\n", string(data))
fmt.Fprintf(w, "\tobject given to template engine was:\n\t\t%+v\n\n", out)
return fmt.Errorf("error executing template %q: %v", p.rawTemplate, err)
}
return nil
}
// TODO: implement HandledResources()
func (p *TemplatePrinter) HandledResources() []string {
return []string{}
}
func (p *TemplatePrinter) IsGeneric() bool {
return true
}
// safeExecute tries to execute the template, but catches panics and returns an error
// should the template engine panic.
func (p *TemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {
var panicErr error
// Sorry for the double anonymous function. There's probably a clever way
// to do this that has the defer'd func setting the value to be returned, but
// that would be even less obvious.
retErr := func() error {
defer func() {
if x := recover(); x != nil {
panicErr = fmt.Errorf("caught panic: %+v", x)
}
}()
return p.template.Execute(w, obj)
}()
if panicErr != nil {
return panicErr
}
return retErr
}

View File

@ -1,99 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"fmt"
"io"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// VersionedPrinter takes runtime objects and ensures they are converted to a given API version
// prior to being passed to a nested printer.
type VersionedPrinter struct {
printer ResourcePrinter
converter runtime.ObjectConvertor
typer runtime.ObjectTyper
versions []schema.GroupVersion
}
// NewVersionedPrinter wraps a printer to convert objects to a known API version prior to printing.
func NewVersionedPrinter(printer ResourcePrinter, converter runtime.ObjectConvertor, typer runtime.ObjectTyper, versions ...schema.GroupVersion) ResourcePrinter {
return &VersionedPrinter{
printer: printer,
converter: converter,
typer: typer,
versions: versions,
}
}
func (p *VersionedPrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj implements ResourcePrinter
func (p *VersionedPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
// if we're unstructured, no conversion necessary
if _, ok := obj.(*unstructured.Unstructured); ok {
return p.printer.PrintObj(obj, w)
}
// if we aren't a generic printer, we don't convert. This means the printer must be aware of what it is getting.
// The default printers fall into this category.
// TODO eventually, all printers must be generic
if !p.IsGeneric() {
return p.printer.PrintObj(obj, w)
}
// if we're already external, no conversion necessary
gvks, _, err := p.typer.ObjectKinds(obj)
if err != nil {
glog.V(1).Info("error determining type for %T, using passed object: %v", obj, err)
return p.printer.PrintObj(obj, w)
}
needsConversion := false
for _, gvk := range gvks {
if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
needsConversion = true
}
}
if !needsConversion {
return p.printer.PrintObj(obj, w)
}
if len(p.versions) == 0 {
return fmt.Errorf("no version specified, object cannot be converted")
}
converted, err := p.converter.ConvertToVersion(obj, schema.GroupVersions(p.versions))
if err != nil {
return err
}
return p.printer.PrintObj(converted, w)
}
// TODO: implement HandledResources()
func (p *VersionedPrinter) HandledResources() []string {
return []string{}
}
func (p *VersionedPrinter) IsGeneric() bool {
return p.printer.IsGeneric()
}