Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
10
vendor/k8s.io/kubernetes/pkg/printers/.import-restrictions
generated
vendored
Normal file
10
vendor/k8s.io/kubernetes/pkg/printers/.import-restrictions
generated
vendored
Normal 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
78
vendor/k8s.io/kubernetes/pkg/printers/BUILD
generated
vendored
Normal 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
6
vendor/k8s.io/kubernetes/pkg/printers/OWNERS
generated
vendored
Normal 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
242
vendor/k8s.io/kubernetes/pkg/printers/customcolumn.go
generated
vendored
Normal 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
|
||||
}
|
||||
109
vendor/k8s.io/kubernetes/pkg/printers/customcolumn_flags.go
generated
vendored
Normal file
109
vendor/k8s.io/kubernetes/pkg/printers/customcolumn_flags.go
generated
vendored
Normal 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: "",
|
||||
}
|
||||
}
|
||||
139
vendor/k8s.io/kubernetes/pkg/printers/customcolumn_flags_test.go
generated
vendored
Normal file
139
vendor/k8s.io/kubernetes/pkg/printers/customcolumn_flags_test.go
generated
vendored
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
372
vendor/k8s.io/kubernetes/pkg/printers/customcolumn_test.go
generated
vendored
Normal file
372
vendor/k8s.io/kubernetes/pkg/printers/customcolumn_test.go
generated
vendored
Normal 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
852
vendor/k8s.io/kubernetes/pkg/printers/humanreadable.go
generated
vendored
Normal 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
|
||||
}
|
||||
134
vendor/k8s.io/kubernetes/pkg/printers/humanreadable_test.go
generated
vendored
Normal file
134
vendor/k8s.io/kubernetes/pkg/printers/humanreadable_test.go
generated
vendored
Normal 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
93
vendor/k8s.io/kubernetes/pkg/printers/interface.go
generated
vendored
Normal 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)
|
||||
}
|
||||
4
vendor/k8s.io/kubernetes/pkg/printers/internalversion/.import-restrictions
generated
vendored
Normal file
4
vendor/k8s.io/kubernetes/pkg/printers/internalversion/.import-restrictions
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Rules": [
|
||||
]
|
||||
}
|
||||
130
vendor/k8s.io/kubernetes/pkg/printers/internalversion/BUILD
generated
vendored
Normal file
130
vendor/k8s.io/kubernetes/pkg/printers/internalversion/BUILD
generated
vendored
Normal 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"],
|
||||
)
|
||||
4023
vendor/k8s.io/kubernetes/pkg/printers/internalversion/describe.go
generated
vendored
Normal file
4023
vendor/k8s.io/kubernetes/pkg/printers/internalversion/describe.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2718
vendor/k8s.io/kubernetes/pkg/printers/internalversion/describe_test.go
generated
vendored
Normal file
2718
vendor/k8s.io/kubernetes/pkg/printers/internalversion/describe_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1794
vendor/k8s.io/kubernetes/pkg/printers/internalversion/printers.go
generated
vendored
Normal file
1794
vendor/k8s.io/kubernetes/pkg/printers/internalversion/printers.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3314
vendor/k8s.io/kubernetes/pkg/printers/internalversion/printers_test.go
generated
vendored
Normal file
3314
vendor/k8s.io/kubernetes/pkg/printers/internalversion/printers_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
49
vendor/k8s.io/kubernetes/pkg/printers/internalversion/sorted_resource_name_list_test.go
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/pkg/printers/internalversion/sorted_resource_name_list_test.go
generated
vendored
Normal 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
159
vendor/k8s.io/kubernetes/pkg/printers/jsonpath.go
generated
vendored
Normal 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
128
vendor/k8s.io/kubernetes/pkg/printers/jsonpath_flags.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
||||
212
vendor/k8s.io/kubernetes/pkg/printers/jsonpath_flags_test.go
generated
vendored
Normal file
212
vendor/k8s.io/kubernetes/pkg/printers/jsonpath_flags_test.go
generated
vendored
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
78
vendor/k8s.io/kubernetes/pkg/printers/kube_template_flags.go
generated
vendored
Normal file
78
vendor/k8s.io/kubernetes/pkg/printers/kube_template_flags.go
generated
vendored
Normal 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
30
vendor/k8s.io/kubernetes/pkg/printers/storage/BUILD
generated
vendored
Normal 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"],
|
||||
)
|
||||
33
vendor/k8s.io/kubernetes/pkg/printers/storage/storage.go
generated
vendored
Normal file
33
vendor/k8s.io/kubernetes/pkg/printers/storage/storage.go
generated
vendored
Normal 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
35
vendor/k8s.io/kubernetes/pkg/printers/tabwriter.go
generated
vendored
Normal 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
119
vendor/k8s.io/kubernetes/pkg/printers/template.go
generated
vendored
Normal 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
133
vendor/k8s.io/kubernetes/pkg/printers/template_flags.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
||||
206
vendor/k8s.io/kubernetes/pkg/printers/template_flags_test.go
generated
vendored
Normal file
206
vendor/k8s.io/kubernetes/pkg/printers/template_flags_test.go
generated
vendored
Normal 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
102
vendor/k8s.io/kubernetes/pkg/printers/template_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user