Add generated file

This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
xing-yang
2018-07-12 10:55:15 -07:00
parent 36b1de0341
commit e213d1890d
17729 changed files with 5090889 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
{
"Rules": [
{
"SelectorRegexp": "k8s[.]io/kubernetes/pkg/(api$|apis/)",
"ForbiddenPrefixes": [
"k8s.io/kubernetes/pkg/printers"
]
}
]
}

78
vendor/k8s.io/kubernetes/pkg/printers/BUILD generated vendored Normal file
View File

@@ -0,0 +1,78 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"customcolumn.go",
"customcolumn_flags.go",
"humanreadable.go",
"interface.go",
"jsonpath.go",
"jsonpath_flags.go",
"kube_template_flags.go",
"tabwriter.go",
"template.go",
"template_flags.go",
],
importpath = "k8s.io/kubernetes/pkg/printers",
deps = [
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/printers/internalversion:all-srcs",
"//pkg/printers/storage:all-srcs",
],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = [
"customcolumn_flags_test.go",
"customcolumn_test.go",
"humanreadable_test.go",
"jsonpath_flags_test.go",
"template_flags_test.go",
"template_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",
],
)

6
vendor/k8s.io/kubernetes/pkg/printers/OWNERS generated vendored Normal file
View File

@@ -0,0 +1,6 @@
approvers:
- smarterclayton
- sig-cli-maintainers
reviewers:
- smarterclayton
- sig-cli

242
vendor/k8s.io/kubernetes/pkg/printers/customcolumn.go generated vendored Normal file
View File

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

View File

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

@@ -0,0 +1,372 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"bytes"
"reflect"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
)
func TestMassageJSONPath(t *testing.T) {
tests := []struct {
input string
expectedOutput string
expectErr bool
}{
{input: "foo.bar", expectedOutput: "{.foo.bar}"},
{input: "{foo.bar}", expectedOutput: "{.foo.bar}"},
{input: ".foo.bar", expectedOutput: "{.foo.bar}"},
{input: "{.foo.bar}", expectedOutput: "{.foo.bar}"},
{input: "", expectedOutput: ""},
{input: "{foo.bar", expectErr: true},
{input: "foo.bar}", expectErr: true},
{input: "{foo.bar}}", expectErr: true},
{input: "{{foo.bar}", expectErr: true},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
output, err := RelaxedJSONPathExpression(test.input)
if err != nil && !test.expectErr {
t.Errorf("unexpected error: %v", err)
return
}
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
}
return
}
if output != test.expectedOutput {
t.Errorf("input: %s, expected: %s, saw: %s", test.input, test.expectedOutput, output)
}
})
}
}
func TestNewColumnPrinterFromSpec(t *testing.T) {
tests := []struct {
spec string
expectedColumns []Column
expectErr bool
name string
noHeaders bool
}{
{
spec: "",
expectErr: true,
name: "empty",
},
{
spec: "invalid",
expectErr: true,
name: "invalid1",
},
{
spec: "invalid=foobar",
expectErr: true,
name: "invalid2",
},
{
spec: "invalid,foobar:blah",
expectErr: true,
name: "invalid3",
},
{
spec: "NAME:metadata.name,API_VERSION:apiVersion",
name: "ok",
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
},
{
spec: "API_VERSION:apiVersion",
name: "no-headers",
noHeaders: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
printer, err := NewCustomColumnsPrinterFromSpec(test.spec, legacyscheme.Codecs.UniversalDecoder(), test.noHeaders)
if test.expectErr {
if err == nil {
t.Errorf("[%s] unexpected non-error", test.name)
}
return
}
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
return
}
if test.noHeaders {
buffer := &bytes.Buffer{}
printer.PrintObj(&api.Pod{}, buffer)
if err != nil {
t.Fatalf("An error occurred printing Pod: %#v", err)
}
if contains(strings.Fields(buffer.String()), "API_VERSION") {
t.Errorf("unexpected header API_VERSION")
}
} else if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
}
})
}
}
func contains(arr []string, s string) bool {
for i := range arr {
if arr[i] == s {
return true
}
}
return false
}
const exampleTemplateOne = `NAME API_VERSION
{metadata.name} {apiVersion}`
const exampleTemplateTwo = `NAME API_VERSION
{metadata.name} {apiVersion}`
func TestNewColumnPrinterFromTemplate(t *testing.T) {
tests := []struct {
spec string
expectedColumns []Column
expectErr bool
name string
}{
{
spec: "",
expectErr: true,
name: "empty",
},
{
spec: "invalid",
expectErr: true,
name: "invalid1",
},
{
spec: "invalid=foobar",
expectErr: true,
name: "invalid2",
},
{
spec: "invalid,foobar:blah",
expectErr: true,
name: "invalid3",
},
{
spec: exampleTemplateOne,
name: "ok",
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
},
{
spec: exampleTemplateTwo,
name: "ok-2",
expectedColumns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
reader := bytes.NewBufferString(test.spec)
printer, err := NewCustomColumnsPrinterFromTemplate(reader, legacyscheme.Codecs.UniversalDecoder())
if test.expectErr {
if err == nil {
t.Errorf("[%s] unexpected non-error", test.name)
}
return
}
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
return
}
if !reflect.DeepEqual(test.expectedColumns, printer.Columns) {
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v\n", test.name, test.expectedColumns, printer.Columns)
}
})
}
}
func TestColumnPrint(t *testing.T) {
tests := []struct {
columns []Column
obj runtime.Object
expectedOutput string
}{
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
expectedOutput: `NAME
foo
`,
},
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
},
obj: &v1.PodList{
Items: []v1.Pod{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
},
},
expectedOutput: `NAME
foo
bar
`,
},
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
expectedOutput: `NAME API_VERSION
foo baz
`,
},
{
columns: []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "API_VERSION",
FieldSpec: "{.apiVersion}",
},
{
Header: "NOT_FOUND",
FieldSpec: "{.notFound}",
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
expectedOutput: `NAME API_VERSION NOT_FOUND
foo baz <none>
`,
},
}
for _, test := range tests {
t.Run(test.expectedOutput, func(t *testing.T) {
printer := &CustomColumnsPrinter{
Columns: test.columns,
Decoder: legacyscheme.Codecs.UniversalDecoder(),
}
buffer := &bytes.Buffer{}
if err := printer.PrintObj(test.obj, buffer); err != nil {
t.Errorf("unexpected error: %v", err)
}
if buffer.String() != test.expectedOutput {
t.Errorf("\nexpected:\n'%s'\nsaw\n'%s'\n", test.expectedOutput, buffer.String())
}
})
}
}
// this mimics how resource/get.go calls the customcolumn printer
func TestIndividualPrintObjOnExistingTabWriter(t *testing.T) {
columns := []Column{
{
Header: "NAME",
FieldSpec: "{.metadata.name}",
},
{
Header: "LONG COLUMN NAME", // name is longer than all values of label1
FieldSpec: "{.metadata.labels.label1}",
},
{
Header: "LABEL 2",
FieldSpec: "{.metadata.labels.label2}",
},
}
objects := []*v1.Pod{
{ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"label1": "foo", "label2": "foo"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{"label1": "bar", "label2": "bar"}}},
}
expectedOutput := `NAME LONG COLUMN NAME LABEL 2
foo foo foo
bar bar bar
`
buffer := &bytes.Buffer{}
tabWriter := GetNewTabWriter(buffer)
printer := &CustomColumnsPrinter{
Columns: columns,
Decoder: legacyscheme.Codecs.UniversalDecoder(),
}
for _, obj := range objects {
if err := printer.PrintObj(obj, tabWriter); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
tabWriter.Flush()
if buffer.String() != expectedOutput {
t.Errorf("\nexpected:\n'%s'\nsaw\n'%s'\n", expectedOutput, buffer.String())
}
}

852
vendor/k8s.io/kubernetes/pkg/printers/humanreadable.go generated vendored Normal file
View File

@@ -0,0 +1,852 @@
/*
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"
"fmt"
"io"
"reflect"
"strings"
"text/tabwriter"
"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"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)
type TablePrinter interface {
PrintTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error)
}
type PrintHandler interface {
Handler(columns, columnsWithWide []string, printFunc interface{}) error
TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error
DefaultTableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error
}
var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
type handlerEntry struct {
columnDefinitions []metav1beta1.TableColumnDefinition
printRows bool
printFunc reflect.Value
args []reflect.Value
}
// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide
// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers
// will only be printed if the object type changes. This makes it useful for printing items
// received from watches.
type HumanReadablePrinter struct {
handlerMap map[reflect.Type]*handlerEntry
defaultHandler *handlerEntry
options PrintOptions
lastType interface{}
skipTabWriter bool
encoder runtime.Encoder
decoder runtime.Decoder
}
var _ PrintHandler = &HumanReadablePrinter{}
// NewHumanReadablePrinter creates a HumanReadablePrinter.
// If encoder and decoder are provided, an attempt to convert unstructured types to internal types is made.
func NewHumanReadablePrinter(decoder runtime.Decoder, options PrintOptions) *HumanReadablePrinter {
printer := &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
options: options,
decoder: decoder,
}
return printer
}
// NewTablePrinter creates a HumanReadablePrinter suitable for calling PrintTable().
func NewTablePrinter() *HumanReadablePrinter {
return &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
}
}
// AddTabWriter sets whether the PrintObj function will format with tabwriter (true
// by default).
func (a *HumanReadablePrinter) AddTabWriter(t bool) *HumanReadablePrinter {
a.skipTabWriter = !t
return a
}
func (a *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePrinter {
for _, fn := range fns {
fn(a)
}
return a
}
// 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
// consecutively, but are separated by non-printer related information.
func (h *HumanReadablePrinter) EnsurePrintHeaders() {
h.options.NoHeaders = false
h.lastType = nil
}
// Handler adds a print handler with a given set of columns to HumanReadablePrinter instance.
// See ValidatePrintHandlerFunc for required method signature.
func (h *HumanReadablePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error {
var columnDefinitions []metav1beta1.TableColumnDefinition
for _, column := range columns {
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
Name: column,
Type: "string",
})
}
for _, column := range columnsWithWide {
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
Name: column,
Type: "string",
Priority: 1,
})
}
printFuncValue := reflect.ValueOf(printFunc)
if err := ValidatePrintHandlerFunc(printFuncValue); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
return err
}
entry := &handlerEntry{
columnDefinitions: columnDefinitions,
printFunc: printFuncValue,
}
objType := printFuncValue.Type().In(0)
if _, ok := h.handlerMap[objType]; ok {
err := fmt.Errorf("registered duplicate printer for %v", objType)
utilruntime.HandleError(err)
return err
}
h.handlerMap[objType] = entry
return nil
}
// TableHandler adds a print handler with a given set of columns to HumanReadablePrinter instance.
// See ValidateRowPrintHandlerFunc for required method signature.
func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
printFuncValue := reflect.ValueOf(printFunc)
if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
return err
}
entry := &handlerEntry{
columnDefinitions: columnDefinitions,
printRows: true,
printFunc: printFuncValue,
}
objType := printFuncValue.Type().In(0)
if _, ok := h.handlerMap[objType]; ok {
err := fmt.Errorf("registered duplicate printer for %v", objType)
utilruntime.HandleError(err)
return err
}
h.handlerMap[objType] = entry
return nil
}
// DefaultTableHandler registers a set of columns and a print func that is given a chance to process
// any object without an explicit handler. Only the most recently set print handler is used.
// See ValidateRowPrintHandlerFunc for required method signature.
func (h *HumanReadablePrinter) DefaultTableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
printFuncValue := reflect.ValueOf(printFunc)
if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
return err
}
entry := &handlerEntry{
columnDefinitions: columnDefinitions,
printRows: true,
printFunc: printFuncValue,
}
h.defaultHandler = entry
return nil
}
// ValidateRowPrintHandlerFunc validates print handler signature.
// printFunc is the function that will be called to print an object.
// It must be of the following type:
// func printFunc(object ObjectType, options PrintOptions) ([]metav1beta1.TableRow, error)
// where ObjectType is the type of the object that will be printed, and the first
// return value is an array of rows, with each row containing a number of cells that
// match the number of columns defined for that printer function.
func ValidateRowPrintHandlerFunc(printFunc reflect.Value) error {
if printFunc.Kind() != reflect.Func {
return fmt.Errorf("invalid print handler. %#v is not a function", printFunc)
}
funcType := printFunc.Type()
if funcType.NumIn() != 2 || funcType.NumOut() != 2 {
return fmt.Errorf("invalid print handler." +
"Must accept 2 parameters and return 2 value.")
}
if funcType.In(1) != reflect.TypeOf((*PrintOptions)(nil)).Elem() ||
funcType.Out(0) != reflect.TypeOf((*[]metav1beta1.TableRow)(nil)).Elem() ||
funcType.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("invalid print handler. The expected signature is: "+
"func handler(obj %v, options PrintOptions) ([]metav1beta1.TableRow, error)", funcType.In(0))
}
return nil
}
// ValidatePrintHandlerFunc validates print handler signature.
// printFunc is the function that will be called to print an object.
// It must be of the following type:
// func printFunc(object ObjectType, w io.Writer, options PrintOptions) error
// where ObjectType is the type of the object that will be printed.
// DEPRECATED: will be replaced with ValidateRowPrintHandlerFunc
func ValidatePrintHandlerFunc(printFunc reflect.Value) error {
if printFunc.Kind() != reflect.Func {
return fmt.Errorf("invalid print handler. %#v is not a function", printFunc)
}
funcType := printFunc.Type()
if funcType.NumIn() != 3 || funcType.NumOut() != 1 {
return fmt.Errorf("invalid print handler." +
"Must accept 3 parameters and return 1 value.")
}
if funcType.In(1) != reflect.TypeOf((*io.Writer)(nil)).Elem() ||
funcType.In(2) != reflect.TypeOf((*PrintOptions)(nil)).Elem() ||
funcType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("invalid print handler. The expected signature is: "+
"func handler(obj %v, w io.Writer, options PrintOptions) error", funcType.In(0))
}
return nil
}
func (h *HumanReadablePrinter) HandledResources() []string {
keys := make([]string, 0)
for k := range h.handlerMap {
// k.String looks like "*api.PodList" and we want just "pod"
api := strings.Split(k.String(), ".")
resource := api[len(api)-1]
if strings.HasSuffix(resource, "List") {
continue
}
resource = strings.ToLower(resource)
keys = append(keys, resource)
}
return keys
}
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
_, err := fmt.Fprintf(w, "Unknown object: %s", string(data))
return err
}
func printHeader(columnNames []string, w io.Writer) error {
if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
return err
}
return nil
}
// PrintObj prints the obj in a human-friendly format according to the type of the obj.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
w, found := output.(*tabwriter.Writer)
if !found && !h.skipTabWriter {
w = GetNewTabWriter(output)
output = w
defer w.Flush()
}
// display tables following the rules of options
if table, ok := obj.(*metav1beta1.Table); ok {
if err := DecorateTable(table, h.options); err != nil {
return err
}
return PrintTable(table, output, h.options)
}
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before
// trying to print, since the printers are keyed by type. This is extremely expensive.
if h.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
}
h.lastType = t
return nil
}
// 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
}
h.lastType = h.defaultHandler
return nil
}
// we failed all reasonable printing efforts, report failure
return fmt.Errorf("error: unknown type %#v", obj)
}
func hasCondition(conditions []metav1beta1.TableRowCondition, t metav1beta1.RowConditionType) bool {
for _, condition := range conditions {
if condition.Type == t {
return condition.Status == metav1beta1.ConditionTrue
}
}
return false
}
// PrintTable prints a table to the provided output respecting the filtering rules for options
// for wide columns and filtered rows. It filters out rows that are Completed. You should call
// DecorateTable if you receive a table from a remote server before calling PrintTable.
func PrintTable(table *metav1beta1.Table, output io.Writer, options PrintOptions) error {
if !options.NoHeaders {
// avoid printing headers if we have no rows to display
if len(table.Rows) == 0 {
return nil
}
first := true
for _, column := range table.ColumnDefinitions {
if !options.Wide && column.Priority != 0 {
continue
}
if first {
first = false
} else {
fmt.Fprint(output, "\t")
}
fmt.Fprint(output, strings.ToUpper(column.Name))
}
fmt.Fprintln(output)
}
for _, row := range table.Rows {
first := true
for i, cell := range row.Cells {
column := table.ColumnDefinitions[i]
if !options.Wide && column.Priority != 0 {
continue
}
if first {
first = false
} else {
fmt.Fprint(output, "\t")
}
if cell != nil {
fmt.Fprint(output, cell)
}
}
fmt.Fprintln(output)
}
return nil
}
// DecorateTable takes a table and attempts to add label columns and the
// namespace column. It will fill empty columns with nil (if the object
// does not expose metadata). It returns an error if the table cannot
// be decorated.
func DecorateTable(table *metav1beta1.Table, options PrintOptions) error {
width := len(table.ColumnDefinitions) + len(options.ColumnLabels)
if options.WithNamespace {
width++
}
if options.ShowLabels {
width++
}
columns := table.ColumnDefinitions
nameColumn := -1
if options.WithKind && !options.Kind.Empty() {
for i := range columns {
if columns[i].Format == "name" && columns[i].Type == "string" {
nameColumn = i
break
}
}
}
if width != len(table.ColumnDefinitions) {
columns = make([]metav1beta1.TableColumnDefinition, 0, width)
if options.WithNamespace {
columns = append(columns, metav1beta1.TableColumnDefinition{
Name: "Namespace",
Type: "string",
})
}
columns = append(columns, table.ColumnDefinitions...)
for _, label := range formatLabelHeaders(options.ColumnLabels) {
columns = append(columns, metav1beta1.TableColumnDefinition{
Name: label,
Type: "string",
})
}
if options.ShowLabels {
columns = append(columns, metav1beta1.TableColumnDefinition{
Name: "Labels",
Type: "string",
})
}
}
rows := table.Rows
includeLabels := len(options.ColumnLabels) > 0 || options.ShowLabels
if includeLabels || options.WithNamespace || nameColumn != -1 {
for i := range rows {
row := rows[i]
if nameColumn != -1 {
row.Cells[nameColumn] = fmt.Sprintf("%s/%s", strings.ToLower(options.Kind.String()), row.Cells[nameColumn])
}
var m metav1.Object
if obj := row.Object.Object; obj != nil {
if acc, err := meta.Accessor(obj); err == nil {
m = acc
}
}
// if we can't get an accessor, fill out the appropriate columns with empty spaces
if m == nil {
if options.WithNamespace {
r := make([]interface{}, 1, width)
row.Cells = append(r, row.Cells...)
}
for j := 0; j < width-len(row.Cells); j++ {
row.Cells = append(row.Cells, nil)
}
rows[i] = row
continue
}
if options.WithNamespace {
r := make([]interface{}, 1, width)
r[0] = m.GetNamespace()
row.Cells = append(r, row.Cells...)
}
if includeLabels {
row.Cells = appendLabelCells(row.Cells, m.GetLabels(), options)
}
rows[i] = row
}
}
table.ColumnDefinitions = columns
table.Rows = rows
return nil
}
// PrintTable returns a table for the provided object, using the printer registered for that type. It returns
// a table that includes all of the information requested by options, but will not remove rows or columns. The
// caller is responsible for applying rules related to filtering rows or columns.
func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error) {
t := reflect.TypeOf(obj)
handler, ok := h.handlerMap[t]
if !ok {
return nil, fmt.Errorf("no table handler registered for this type %v", t)
}
if !handler.printRows {
return h.legacyPrinterToTable(obj, handler)
}
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
results := handler.printFunc.Call(args)
if !results[1].IsNil() {
return nil, results[1].Interface().(error)
}
columns := handler.columnDefinitions
if !options.Wide {
columns = make([]metav1beta1.TableColumnDefinition, 0, len(handler.columnDefinitions))
for i := range handler.columnDefinitions {
if handler.columnDefinitions[i].Priority != 0 {
continue
}
columns = append(columns, handler.columnDefinitions[i])
}
}
table := &metav1beta1.Table{
ListMeta: metav1.ListMeta{
ResourceVersion: "",
},
ColumnDefinitions: columns,
Rows: results[0].Interface().([]metav1beta1.TableRow),
}
if m, err := meta.ListAccessor(obj); err == nil {
table.ResourceVersion = m.GetResourceVersion()
table.SelfLink = m.GetSelfLink()
table.Continue = m.GetContinue()
} else {
if m, err := meta.CommonAccessor(obj); err == nil {
table.ResourceVersion = m.GetResourceVersion()
table.SelfLink = m.GetSelfLink()
}
}
if err := DecorateTable(table, options); err != nil {
return nil, err
}
return table, nil
}
// printRowsForHandlerEntry prints the incremental table output (headers if the current type is
// different from lastType) including all the rows in the object. It returns the current type
// or an error, if any.
func printRowsForHandlerEntry(output io.Writer, handler *handlerEntry, obj runtime.Object, options PrintOptions, includeHeaders bool) error {
var results []reflect.Value
if handler.printRows {
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
results = handler.printFunc.Call(args)
if !results[1].IsNil() {
return results[1].Interface().(error)
}
}
if includeHeaders {
var headers []string
for _, column := range handler.columnDefinitions {
if column.Priority != 0 && !options.Wide {
continue
}
headers = append(headers, strings.ToUpper(column.Name))
}
headers = append(headers, formatLabelHeaders(options.ColumnLabels)...)
// LABELS is always the last column.
headers = append(headers, formatShowLabelsHeader(options.ShowLabels)...)
if options.WithNamespace {
headers = append(withNamespacePrefixColumns, headers...)
}
printHeader(headers, output)
}
if !handler.printRows {
// TODO: this code path is deprecated and will be removed when all handlers are row printers
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(output), reflect.ValueOf(options)}
resultValue := handler.printFunc.Call(args)[0]
if resultValue.IsNil() {
return nil
}
return resultValue.Interface().(error)
}
if results[1].IsNil() {
rows := results[0].Interface().([]metav1beta1.TableRow)
printRows(output, rows, options)
return nil
}
return results[1].Interface().(error)
}
// printRows writes the provided rows to output.
func printRows(output io.Writer, rows []metav1beta1.TableRow, options PrintOptions) {
for _, row := range rows {
if options.WithNamespace {
if obj := row.Object.Object; obj != nil {
if m, err := meta.Accessor(obj); err == nil {
fmt.Fprint(output, m.GetNamespace())
}
}
fmt.Fprint(output, "\t")
}
for i, cell := range row.Cells {
if i != 0 {
fmt.Fprint(output, "\t")
} else {
// TODO: remove this once we drop the legacy printers
if options.WithKind && !options.Kind.Empty() {
fmt.Fprintf(output, "%s/%s", strings.ToLower(options.Kind.String()), cell)
continue
}
}
fmt.Fprint(output, cell)
}
hasLabels := len(options.ColumnLabels) > 0
if obj := row.Object.Object; obj != nil && (hasLabels || options.ShowLabels) {
if m, err := meta.Accessor(obj); err == nil {
for _, value := range labelValues(m.GetLabels(), options) {
output.Write([]byte("\t"))
output.Write([]byte(value))
}
}
}
output.Write([]byte("\n"))
}
}
// legacyPrinterToTable uses the old printFunc with tabbed writer to generate a table.
// TODO: remove when all legacy printers are removed.
func (h *HumanReadablePrinter) legacyPrinterToTable(obj runtime.Object, handler *handlerEntry) (*metav1beta1.Table, error) {
printFunc := handler.printFunc
table := &metav1beta1.Table{
ColumnDefinitions: handler.columnDefinitions,
}
options := PrintOptions{
NoHeaders: true,
Wide: true,
}
buf := &bytes.Buffer{}
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(buf), reflect.ValueOf(options)}
if meta.IsListType(obj) {
// TODO: this uses more memory than it has to, as we refactor printers we should remove the need
// for this.
args[0] = reflect.ValueOf(obj)
resultValue := printFunc.Call(args)[0]
if !resultValue.IsNil() {
return nil, resultValue.Interface().(error)
}
data := buf.Bytes()
i := 0
items, err := meta.ExtractList(obj)
if err != nil {
return nil, err
}
for len(data) > 0 {
cells, remainder := tabbedLineToCells(data, len(table.ColumnDefinitions))
table.Rows = append(table.Rows, metav1beta1.TableRow{
Cells: cells,
Object: runtime.RawExtension{Object: items[i]},
})
data = remainder
i++
}
} else {
args[0] = reflect.ValueOf(obj)
resultValue := printFunc.Call(args)[0]
if !resultValue.IsNil() {
return nil, resultValue.Interface().(error)
}
data := buf.Bytes()
cells, _ := tabbedLineToCells(data, len(table.ColumnDefinitions))
table.Rows = append(table.Rows, metav1beta1.TableRow{
Cells: cells,
Object: runtime.RawExtension{Object: obj},
})
}
return table, nil
}
// TODO: this method assumes the meta/v1 server API, so should be refactored out of this package
func printUnstructured(unstructured runtime.Unstructured, w io.Writer, additionalFields []string, options PrintOptions) error {
metadata, err := meta.Accessor(unstructured)
if err != nil {
return err
}
if options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", metadata.GetNamespace()); err != nil {
return err
}
}
content := unstructured.UnstructuredContent()
kind := "<missing>"
if objKind, ok := content["kind"]; ok {
if str, ok := objKind.(string); ok {
kind = str
}
}
if objAPIVersion, ok := content["apiVersion"]; ok {
if str, ok := objAPIVersion.(string); ok {
version, err := schema.ParseGroupVersion(str)
if err != nil {
return err
}
kind = kind + "." + version.Version + "." + version.Group
}
}
name := FormatResourceName(options.Kind, metadata.GetName(), options.WithKind)
if _, err := fmt.Fprintf(w, "%s\t%s", name, kind); err != nil {
return err
}
for _, field := range additionalFields {
if value, ok := content[field]; ok {
var formattedValue string
switch typedValue := value.(type) {
case []interface{}:
formattedValue = fmt.Sprintf("%d item(s)", len(typedValue))
default:
formattedValue = fmt.Sprintf("%v", value)
}
if _, err := fmt.Fprintf(w, "\t%s", formattedValue); err != nil {
return err
}
}
}
if _, err := fmt.Fprint(w, AppendLabels(metadata.GetLabels(), options.ColumnLabels)); err != nil {
return err
}
if _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, metadata.GetLabels())); err != nil {
return err
}
return nil
}
func formatLabelHeaders(columnLabels []string) []string {
formHead := make([]string, len(columnLabels))
for i, l := range columnLabels {
p := strings.Split(l, "/")
formHead[i] = strings.ToUpper((p[len(p)-1]))
}
return formHead
}
// headers for --show-labels=true
func formatShowLabelsHeader(showLabels bool) []string {
if showLabels {
return []string{"LABELS"}
}
return nil
}
// labelValues returns a slice of value columns matching the requested print options.
func labelValues(itemLabels map[string]string, opts PrintOptions) []string {
var values []string
for _, key := range opts.ColumnLabels {
values = append(values, itemLabels[key])
}
if opts.ShowLabels {
values = append(values, labels.FormatLabels(itemLabels))
}
return values
}
// appendLabelCells returns a slice of value columns matching the requested print options.
// Intended for use with tables.
func appendLabelCells(values []interface{}, itemLabels map[string]string, opts PrintOptions) []interface{} {
for _, key := range opts.ColumnLabels {
values = append(values, itemLabels[key])
}
if opts.ShowLabels {
values = append(values, labels.FormatLabels(itemLabels))
}
return values
}
// FormatResourceName receives a resource kind, name, and boolean specifying
// whether or not to update the current name to "kind/name"
func FormatResourceName(kind schema.GroupKind, name string, withKind bool) string {
if !withKind || kind.Empty() {
return name
}
return strings.ToLower(kind.String()) + "/" + name
}
func AppendLabels(itemLabels map[string]string, columnLabels []string) string {
var buffer bytes.Buffer
for _, cl := range columnLabels {
buffer.WriteString(fmt.Sprint("\t"))
if il, ok := itemLabels[cl]; ok {
buffer.WriteString(fmt.Sprint(il))
} else {
buffer.WriteString("<none>")
}
}
return buffer.String()
}
// Append all labels to a single column. We need this even when show-labels flag* is
// false, since this adds newline delimiter to the end of each row.
func AppendAllLabels(showLabels bool, itemLabels map[string]string) string {
var buffer bytes.Buffer
if showLabels {
buffer.WriteString(fmt.Sprint("\t"))
buffer.WriteString(labels.FormatLabels(itemLabels))
}
buffer.WriteString("\n")
return buffer.String()
}
// check if the object is unstructured. If so, attempt to convert it to a type we can understand.
func decodeUnknownObject(obj runtime.Object, decoder runtime.Decoder) (runtime.Object, error) {
var err error
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
}
func tabbedLineToCells(data []byte, expected int) ([]interface{}, []byte) {
var remainder []byte
max := bytes.Index(data, []byte("\n"))
if max != -1 {
remainder = data[max+1:]
data = data[:max]
}
cells := make([]interface{}, expected)
for i := 0; i < expected; i++ {
next := bytes.Index(data, []byte("\t"))
if next == -1 {
cells[i] = string(data)
// fill the remainder with empty strings, this indicates a printer bug
for j := i + 1; j < expected; j++ {
cells[j] = ""
}
break
}
cells[i] = string(data[:next])
data = data[next+1:]
}
return cells, remainder
}

View File

@@ -0,0 +1,134 @@
/*
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"
"fmt"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
api "k8s.io/kubernetes/pkg/apis/core"
)
var testNamespaceColumnDefinitions = []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Status", Type: "string", Description: "The status of the namespace"},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
func testPrintNamespace(obj *api.Namespace, options PrintOptions) ([]metav1beta1.TableRow, error) {
if options.WithNamespace {
return nil, fmt.Errorf("namespace is not namespaced")
}
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, obj.Status.Phase, "<unknow>")
return []metav1beta1.TableRow{row}, nil
}
func TestPrintRowsForHandlerEntry(t *testing.T) {
printFunc := reflect.ValueOf(testPrintNamespace)
testCase := []struct {
name string
h *handlerEntry
opt PrintOptions
obj runtime.Object
includeHeader bool
expectOut string
expectErr string
}{
{
name: "no tablecolumndefinition and includeheader flase",
h: &handlerEntry{
columnDefinitions: []metav1beta1.TableColumnDefinition{},
printRows: true,
printFunc: printFunc,
},
opt: PrintOptions{},
obj: &api.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
},
includeHeader: false,
expectOut: "test\t\t<unknow>\n",
},
{
name: "no tablecolumndefinition and includeheader true",
h: &handlerEntry{
columnDefinitions: []metav1beta1.TableColumnDefinition{},
printRows: true,
printFunc: printFunc,
},
opt: PrintOptions{},
obj: &api.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
},
includeHeader: true,
expectOut: "\ntest\t\t<unknow>\n",
},
{
name: "have tablecolumndefinition and includeheader true",
h: &handlerEntry{
columnDefinitions: testNamespaceColumnDefinitions,
printRows: true,
printFunc: printFunc,
},
opt: PrintOptions{},
obj: &api.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
},
includeHeader: true,
expectOut: "NAME\tSTATUS\tAGE\ntest\t\t<unknow>\n",
},
{
name: "print namespace and withnamespace true, should not print header",
h: &handlerEntry{
columnDefinitions: testNamespaceColumnDefinitions,
printRows: true,
printFunc: printFunc,
},
opt: PrintOptions{
WithNamespace: true,
},
obj: &api.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
},
includeHeader: true,
expectOut: "",
expectErr: "namespace is not namespaced",
},
}
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", test.name, test.expectOut, buffer.String())
}
})
}
}

93
vendor/k8s.io/kubernetes/pkg/printers/interface.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
/*
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"
"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
}
// ResourcePrinterFunc is a function that can print objects
type ResourcePrinterFunc func(runtime.Object, io.Writer) error
// PrintObj implements ResourcePrinter
func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error {
return fn(obj, w)
}
type PrintOptions struct {
// supported Format types can be found in pkg/printers/printers.go
OutputFormatType string
OutputFormatArgument string
NoHeaders bool
WithNamespace bool
WithKind bool
Wide bool
ShowAll bool
ShowLabels bool
AbsoluteTimestamps bool
Kind schema.GroupKind
ColumnLabels []string
SortBy string
// indicates if it is OK to ignore missing keys for rendering an output template.
AllowMissingKeys bool
}
// Describer generates output for the named resource or an error
// if the output could not be generated. Implementers typically
// abstract the retrieval of the named object from a remote server.
type Describer interface {
Describe(namespace, name string, describerSettings DescriberSettings) (output string, err error)
}
// DescriberSettings holds display configuration for each object
// describer to control what is printed.
type DescriberSettings struct {
ShowEvents bool
}
// ObjectDescriber is an interface for displaying arbitrary objects with extra
// information. Use when an object is in hand (on disk, or already retrieved).
// Implementers may ignore the additional information passed on extra, or use it
// by default. ObjectDescribers may return ErrNoDescriber if no suitable describer
// is found.
type ObjectDescriber interface {
DescribeObject(object interface{}, extra ...interface{}) (output string, err error)
}
// ErrNoDescriber is a structured error indicating the provided object or objects
// cannot be described.
type ErrNoDescriber struct {
Types []string
}
// Error implements the error interface.
func (e ErrNoDescriber) Error() string {
return fmt.Sprintf("no describer has been defined for %v", e.Types)
}

View File

@@ -0,0 +1,4 @@
{
"Rules": [
]
}

View File

@@ -0,0 +1,130 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"describe_test.go",
"printers_test.go",
"sorted_resource_name_list_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/networking:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/pointer:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/yaml:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"describe.go",
"printers.go",
],
importpath = "k8s.io/kubernetes/pkg/printers/internalversion",
deps = [
"//pkg/api/events:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/ref:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/core/helper/qos:go_default_library",
"//pkg/apis/extensions:go_default_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",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/fieldpath:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/slice:go_default_library",
"//vendor/github.com/fatih/camelcase:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internalversion
import (
api "k8s.io/kubernetes/pkg/apis/core"
"reflect"
"sort"
"testing"
)
func TestSortableResourceNamesSorting(t *testing.T) {
want := SortableResourceNames{
api.ResourceName(""),
api.ResourceName("42"),
api.ResourceName("bar"),
api.ResourceName("foo"),
api.ResourceName("foo"),
api.ResourceName("foobar"),
}
in := SortableResourceNames{
api.ResourceName("foo"),
api.ResourceName("42"),
api.ResourceName("foobar"),
api.ResourceName("foo"),
api.ResourceName("bar"),
api.ResourceName(""),
}
sort.Sort(in)
if !reflect.DeepEqual(in, want) {
t.Errorf("got %v, want %v", in, want)
}
}

159
vendor/k8s.io/kubernetes/pkg/printers/jsonpath.go generated vendored Normal file
View File

@@ -0,0 +1,159 @@
/*
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"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
)
// 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{
rawTemplate: tmpl,
JSONPath: j,
}, nil
}
// PrintObj formats the obj with the JSONPath Template.
func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
// we need an actual value in order to retrieve the package path for an object.
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
return fmt.Errorf(printers.InternalObjectPrinterErr)
}
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
}

128
vendor/k8s.io/kubernetes/pkg/printers/jsonpath_flags.go generated vendored Normal file
View File

@@ -0,0 +1,128 @@
/*
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"
"io/ioutil"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// templates are logically optional for specifying a format.
// this allows a user to specify a template format value
// as --output=jsonpath=
var jsonFormats = map[string]bool{
"jsonpath": true,
"jsonpath-file": true,
}
// JSONPathPrintFlags provides default flags necessary for template printing.
// Given the following flag values, a printer can be requested that knows
// how to handle printing based on these values.
type JSONPathPrintFlags struct {
// indicates if it is OK to ignore missing keys for rendering
// an output template.
AllowMissingKeys *bool
TemplateArgument *string
}
func (f *JSONPathPrintFlags) AllowedFormats() []string {
formats := make([]string, 0, len(jsonFormats))
for format := range jsonFormats {
formats = append(formats, format)
}
return formats
}
// ToPrinter receives an templateFormat and returns a printer capable of
// handling --template format printing.
// Returns false if the specified templateFormat does not match a template format.
func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
}
templateValue := ""
if f.TemplateArgument == nil || len(*f.TemplateArgument) == 0 {
for format := range jsonFormats {
format = format + "="
if strings.HasPrefix(templateFormat, format) {
templateValue = templateFormat[len(format):]
templateFormat = format[:len(format)-1]
break
}
}
} else {
templateValue = *f.TemplateArgument
}
if _, supportedFormat := jsonFormats[templateFormat]; !supportedFormat {
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
}
if len(templateValue) == 0 {
return nil, fmt.Errorf("template format specified but no template given")
}
if templateFormat == "jsonpath-file" {
data, err := ioutil.ReadFile(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
}
templateValue = string(data)
}
p, err := NewJSONPathPrinter(templateValue)
if err != nil {
return nil, fmt.Errorf("error parsing jsonpath %s, %v\n", templateValue, err)
}
allowMissingKeys := true
if f.AllowMissingKeys != nil {
allowMissingKeys = *f.AllowMissingKeys
}
p.AllowMissingKeys(allowMissingKeys)
return p, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *JSONPathPrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when --output=jsonpath, --output=jsonpath-file.")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewJSONPathPrintFlags returns flags associated with
// --template printing, with default values set.
func NewJSONPathPrintFlags(templateValue string, allowMissingKeys bool) *JSONPathPrintFlags {
return &JSONPathPrintFlags{
TemplateArgument: &templateValue,
AllowMissingKeys: &allowMissingKeys,
}
}

View File

@@ -0,0 +1,212 @@
/*
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 TestPrinterSupportsExpectedJSONPathFormats(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
jsonpathFile, 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())
}(jsonpathFile)
fmt.Fprintf(jsonpathFile, "{ .metadata.name }\n")
testCases := []struct {
name string
outputFormat string
templateArg string
expectedError string
expectedParseError string
expectedOutput string
expectNoMatch bool
}{
{
name: "valid output format also containing the jsonpath argument succeeds",
outputFormat: "jsonpath={ .metadata.name }",
expectedOutput: "foo",
},
{
name: "valid output format and no --template argument results in an error",
outputFormat: "jsonpath",
expectedError: "template format specified but no template given",
},
{
name: "valid output format and --template argument succeeds",
outputFormat: "jsonpath",
templateArg: "{ .metadata.name }",
expectedOutput: "foo",
},
{
name: "jsonpath template file should match, and successfully return correct value",
outputFormat: "jsonpath-file",
templateArg: jsonpathFile.Name(),
expectedOutput: "foo",
},
{
name: "valid output format and invalid --template argument results in a parsing from the printer",
outputFormat: "jsonpath",
templateArg: "{invalid}",
expectedParseError: "unrecognized identifier invalid",
},
{
name: "no printer is matched on an invalid outputFormat",
outputFormat: "invalid",
expectNoMatch: true,
},
{
name: "jsonpath 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) {
templateArg := &tc.templateArg
if len(tc.templateArg) == 0 {
templateArg = nil
}
printFlags := JSONPathPrintFlags{
TemplateArgument: 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())
}
})
}
}
func TestJSONPathPrinterDefaultsAllowMissingKeysToTrue(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
allowMissingKeys := false
testCases := []struct {
name string
templateArg string
expectedOutput string
expectedError string
allowMissingKeys *bool
}{
{
name: "existing field does not error and returns expected value",
templateArg: "{ .metadata.name }",
expectedOutput: "foo",
allowMissingKeys: &allowMissingKeys,
},
{
name: "missing field does not error and returns an empty string since missing keys are allowed by default",
templateArg: "{ .metadata.missing }",
expectedOutput: "",
allowMissingKeys: nil,
},
{
name: "missing field returns expected error if field is missing and allowMissingKeys is explicitly set to false",
templateArg: "{ .metadata.missing }",
expectedError: "error executing jsonpath",
allowMissingKeys: &allowMissingKeys,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
printFlags := JSONPathPrintFlags{
TemplateArgument: &tc.templateArg,
AllowMissingKeys: tc.allowMissingKeys,
}
outputFormat := "jsonpath"
p, err := printFlags.ToPrinter(outputFormat)
if genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected to match template printer for output format %q", outputFormat)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
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.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}

View File

@@ -0,0 +1,78 @@
/*
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 (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// KubeTemplatePrintFlags composes print flags that provide both a JSONPath and a go-template printer.
// This is necessary if dealing with cases that require support both both printers, since both sets of flags
// require overlapping flags.
type KubeTemplatePrintFlags struct {
*GoTemplatePrintFlags
*JSONPathPrintFlags
AllowMissingKeys *bool
TemplateArgument *string
}
func (f *KubeTemplatePrintFlags) AllowedFormats() []string {
return append(f.GoTemplatePrintFlags.AllowedFormats(), f.JSONPathPrintFlags.AllowedFormats()...)
}
func (f *KubeTemplatePrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, error) {
if p, err := f.JSONPathPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return p, err
}
return f.GoTemplatePrintFlags.ToPrinter(outputFormat)
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *KubeTemplatePrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewKubeTemplatePrintFlags returns flags associated with
// --template printing, with default values set.
func NewKubeTemplatePrintFlags() *KubeTemplatePrintFlags {
allowMissingKeysPtr := true
templateArgPtr := ""
return &KubeTemplatePrintFlags{
GoTemplatePrintFlags: &GoTemplatePrintFlags{
TemplateArgument: &templateArgPtr,
AllowMissingKeys: &allowMissingKeysPtr,
},
JSONPathPrintFlags: &JSONPathPrintFlags{
TemplateArgument: &templateArgPtr,
AllowMissingKeys: &allowMissingKeysPtr,
},
TemplateArgument: &templateArgPtr,
AllowMissingKeys: &allowMissingKeysPtr,
}
}

30
vendor/k8s.io/kubernetes/pkg/printers/storage/BUILD generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/printers/storage",
deps = [
"//pkg/printers:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,33 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storage
import (
"context"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/printers"
)
type TableConvertor struct {
printers.TablePrinter
}
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})
}

35
vendor/k8s.io/kubernetes/pkg/printers/tabwriter.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
/*
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 (
"io"
"text/tabwriter"
)
const (
tabwriterMinWidth = 10
tabwriterWidth = 4
tabwriterPadding = 3
tabwriterPadChar = ' '
tabwriterFlags = 0
)
// GetNewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text.
func GetNewTabWriter(output io.Writer) *tabwriter.Writer {
return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)
}

119
vendor/k8s.io/kubernetes/pkg/printers/template.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
/*
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/base64"
"encoding/json"
"fmt"
"io"
"reflect"
"text/template"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
)
// GoTemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type GoTemplatePrinter struct {
rawTemplate string
template *template.Template
}
func NewGoTemplatePrinter(tmpl []byte) (*GoTemplatePrinter, error) {
t, err := template.New("output").
Funcs(template.FuncMap{
"exists": exists,
"base64decode": base64decode,
}).
Parse(string(tmpl))
if err != nil {
return nil, err
}
return &GoTemplatePrinter{
rawTemplate: string(tmpl),
template: t,
}, nil
}
// AllowMissingKeys tells the template engine if missing keys are allowed.
func (p *GoTemplatePrinter) AllowMissingKeys(allow bool) {
if allow {
p.template.Option("missingkey=default")
} else {
p.template.Option("missingkey=error")
}
}
// PrintObj formats the obj with the Go Template.
func (p *GoTemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
return fmt.Errorf(printers.InternalObjectPrinterErr)
}
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
}
// safeExecute tries to execute the template, but catches panics and returns an error
// should the template engine panic.
func (p *GoTemplatePrinter) 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
}
func base64decode(v string) (string, error) {
data, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return "", fmt.Errorf("base64 decode failed: %v", err)
}
return string(data), nil
}

133
vendor/k8s.io/kubernetes/pkg/printers/template_flags.go generated vendored Normal file
View File

@@ -0,0 +1,133 @@
/*
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"
"io/ioutil"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// templates are logically optional for specifying a format.
// this allows a user to specify a template format value
// as --output=go-template=
var templateFormats = map[string]bool{
"template": true,
"go-template": true,
"go-template-file": true,
"templatefile": true,
}
// GoTemplatePrintFlags provides default flags necessary for template printing.
// Given the following flag values, a printer can be requested that knows
// how to handle printing based on these values.
type GoTemplatePrintFlags struct {
// indicates if it is OK to ignore missing keys for rendering
// an output template.
AllowMissingKeys *bool
TemplateArgument *string
}
func (f *GoTemplatePrintFlags) AllowedFormats() []string {
formats := make([]string, 0, len(templateFormats))
for format := range templateFormats {
formats = append(formats, format)
}
return formats
}
// ToPrinter receives an templateFormat and returns a printer capable of
// handling --template format printing.
// Returns false if the specified templateFormat does not match a template format.
func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
}
templateValue := ""
if f.TemplateArgument == nil || len(*f.TemplateArgument) == 0 {
for format := range templateFormats {
format = format + "="
if strings.HasPrefix(templateFormat, format) {
templateValue = templateFormat[len(format):]
templateFormat = format[:len(format)-1]
break
}
}
} else {
templateValue = *f.TemplateArgument
}
if _, supportedFormat := templateFormats[templateFormat]; !supportedFormat {
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
}
if len(templateValue) == 0 {
return nil, fmt.Errorf("template format specified but no template given")
}
if templateFormat == "templatefile" || templateFormat == "go-template-file" {
data, err := ioutil.ReadFile(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
}
templateValue = string(data)
}
p, err := NewGoTemplatePrinter([]byte(templateValue))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", templateValue, err)
}
allowMissingKeys := true
if f.AllowMissingKeys != nil {
allowMissingKeys = *f.AllowMissingKeys
}
p.AllowMissingKeys(allowMissingKeys)
return p, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *GoTemplatePrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewGoTemplatePrintFlags returns flags associated with
// --template printing, with default values set.
func NewGoTemplatePrintFlags() *GoTemplatePrintFlags {
allowMissingKeysPtr := true
templateValuePtr := ""
return &GoTemplatePrintFlags{
TemplateArgument: &templateValuePtr,
AllowMissingKeys: &allowMissingKeysPtr,
}
}

View File

@@ -0,0 +1,206 @@
/*
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 TestPrinterSupportsExpectedTemplateFormats(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
templateFile, 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())
}(templateFile)
fmt.Fprintf(templateFile, "{{ .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 template argument succeeds",
outputFormat: "go-template={{ .metadata.name }}",
expectedOutput: "foo",
},
{
name: "valid output format and no template argument results in an error",
outputFormat: "template",
expectedError: "template format specified but no template given",
},
{
name: "valid output format and template argument succeeds",
outputFormat: "go-template",
templateArg: "{{ .metadata.name }}",
expectedOutput: "foo",
},
{
name: "Go-template file should match, and successfully return correct value",
outputFormat: "go-template-file",
templateArg: templateFile.Name(),
expectedOutput: "foo",
},
{
name: "valid output format and invalid template argument results in the templateArg contents as the output",
outputFormat: "go-template",
templateArg: "invalid",
expectedOutput: "invalid",
},
{
name: "no printer is matched on an invalid outputFormat",
outputFormat: "invalid",
expectNoMatch: true,
},
{
name: "go-template printer should not match on any other format supported by another printer",
outputFormat: "jsonpath",
expectNoMatch: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
templateArg := &tc.templateArg
if len(tc.templateArg) == 0 {
templateArg = nil
}
printFlags := GoTemplatePrintFlags{
TemplateArgument: 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 err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}
func TestTemplatePrinterDefaultsAllowMissingKeysToTrue(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
allowMissingKeys := false
testCases := []struct {
name string
templateArg string
expectedOutput string
expectedError string
allowMissingKeys *bool
}{
{
name: "existing field does not error and returns expected value",
templateArg: "{{ .metadata.name }}",
expectedOutput: "foo",
allowMissingKeys: &allowMissingKeys,
},
{
name: "missing field does not error and returns no value since missing keys are allowed by default",
templateArg: "{{ .metadata.missing }}",
expectedOutput: "<no value>",
allowMissingKeys: nil,
},
{
name: "missing field returns expected error if field is missing and allowMissingKeys is explicitly set to false",
templateArg: "{{ .metadata.missing }}",
expectedError: "error executing template",
allowMissingKeys: &allowMissingKeys,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
printFlags := GoTemplatePrintFlags{
TemplateArgument: &tc.templateArg,
AllowMissingKeys: tc.allowMissingKeys,
}
outputFormat := "template"
p, err := printFlags.ToPrinter(outputFormat)
if genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected to match template printer for output format %q", outputFormat)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
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.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}

102
vendor/k8s.io/kubernetes/pkg/printers/template_test.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
/*
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"
"strings"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestTemplate(t *testing.T) {
testCase := []struct {
name string
template string
obj runtime.Object
expectOut string
expectErr func(error) (string, bool)
}{
{
name: "support base64 decoding of secret data",
template: "{{ .data.username | base64decode }}",
obj: &v1.Secret{
Data: map[string][]byte{
"username": []byte("hunter"),
},
},
expectOut: "hunter",
},
{
name: "test error path for base64 decoding",
template: "{{ .data.username | base64decode }}",
obj: &badlyMarshaledSecret{},
expectErr: func(err error) (string, bool) {
matched := strings.Contains(err.Error(), "base64 decode")
return "a base64 decode error", matched
},
},
}
for _, test := range testCase {
t.Run(test.name, func(t *testing.T) {
buffer := &bytes.Buffer{}
p, err := NewGoTemplatePrinter([]byte(test.template))
if err != nil {
if test.expectErr == nil {
t.Errorf("[%s]expected success but got:\n %v\n", test.name, err)
return
}
if expected, ok := test.expectErr(err); !ok {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, expected, err)
}
return
}
err = p.PrintObj(test.obj, buffer)
if err != nil {
if test.expectErr == nil {
t.Errorf("[%s]expected success but got:\n %v\n", test.name, err)
return
}
if expected, ok := test.expectErr(err); !ok {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, expected, err)
}
return
}
if test.expectErr != nil {
t.Errorf("[%s]expect:\n error\n but got:\n no error\n", test.name)
return
}
if test.expectOut != buffer.String() {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, test.expectOut, buffer.String())
}
})
}
}
type badlyMarshaledSecret struct {
v1.Secret
}
func (a badlyMarshaledSecret) MarshalJSON() ([]byte, error) {
return []byte(`{"apiVersion":"v1","data":{"username":"--THIS IS NOT BASE64--"},"kind":"Secret"}`), nil
}