wire through template PrintFlags

This commit is contained in:
juanvallejo 2018-03-16 18:39:38 -04:00
parent 90c09c75d6
commit ec672ca279
No known key found for this signature in database
GPG Key ID: 7D2C958002D6448D
10 changed files with 766 additions and 62 deletions

View File

@ -16,11 +16,14 @@ go_library(
"json.go",
"json_yaml_flags.go",
"jsonpath.go",
"jsonpath_flags.go",
"kube_template_flags.go",
"name.go",
"name_flags.go",
"printers.go",
"tabwriter.go",
"template.go",
"template_flags.go",
"versioned.go",
],
importpath = "k8s.io/kubernetes/pkg/printers",
@ -48,7 +51,9 @@ go_test(
"customcolumn_flags_test.go",
"customcolumn_test.go",
"json_yaml_flags_test.go",
"jsonpath_flags_test.go",
"name_flags_test.go",
"template_flags_test.go",
],
deps = [
":go_default_library",

View File

@ -309,7 +309,7 @@ func TestBadPrinter(t *testing.T) {
}{
{"empty template", &printers.PrintOptions{OutputFormatType: "template", AllowMissingKeys: false}, fmt.Errorf("template format specified but no template given")},
{"bad template", &printers.PrintOptions{OutputFormatType: "template", OutputFormatArgument: "{{ .Name", AllowMissingKeys: false}, fmt.Errorf("error parsing template {{ .Name, template: output:1: unclosed action\n")},
{"bad templatefile", &printers.PrintOptions{OutputFormatType: "templatefile", AllowMissingKeys: false}, fmt.Errorf("templatefile format specified but no template file given")},
{"bad templatefile", &printers.PrintOptions{OutputFormatType: "templatefile", AllowMissingKeys: false}, fmt.Errorf("template format specified but no template given")},
{"bad jsonpath", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.Name", AllowMissingKeys: false}, fmt.Errorf("error parsing jsonpath {.Name, unclosed action\n")},
{"unknown format", &printers.PrintOptions{OutputFormatType: "anUnknownFormat", OutputFormatArgument: "", AllowMissingKeys: false}, fmt.Errorf("output format \"anUnknownFormat\" not recognized")},
}
@ -462,7 +462,7 @@ func TestUnknownTypePrinting(t *testing.T) {
func TestTemplatePanic(t *testing.T) {
tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}`
printer, err := printers.NewTemplatePrinter([]byte(tmpl))
printer, err := printers.NewGoTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
@ -618,7 +618,7 @@ func TestTemplateStrings(t *testing.T) {
}
// The point of this test is to verify that the below template works.
tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}`
p, err := printers.NewTemplatePrinter([]byte(tmpl))
p, err := printers.NewGoTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
@ -652,13 +652,13 @@ func TestPrinters(t *testing.T) {
jsonpathPrinter printers.ResourcePrinter
)
templatePrinter, err = printers.NewTemplatePrinter([]byte("{{.name}}"))
templatePrinter, err = printers.NewGoTemplatePrinter([]byte("{{.name}}"))
if err != nil {
t.Fatal(err)
}
templatePrinter = printers.NewVersionedPrinter(templatePrinter, legacyscheme.Scheme, legacyscheme.Scheme, v1.SchemeGroupVersion)
templatePrinter2, err = printers.NewTemplatePrinter([]byte("{{len .items}}"))
templatePrinter2, err = printers.NewGoTemplatePrinter([]byte("{{len .items}}"))
if err != nil {
t.Fatal(err)
}

View File

@ -0,0 +1,118 @@
/*
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"
)
// 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
}
// 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, bool, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, false, fmt.Errorf("missing --template value")
}
templateValue := ""
// templates are logically optional for specifying a format.
// this allows a user to specify a template format value
// as --output=jsonpath=
templateFormats := map[string]bool{
"jsonpath": true,
"jsonpath-file": true,
}
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, false, nil
}
if len(templateValue) == 0 {
return nil, true, fmt.Errorf("template format specified but no template given")
}
if templateFormat == "jsonpath-file" {
data, err := ioutil.ReadFile(templateValue)
if err != nil {
return nil, true, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
}
templateValue = string(data)
}
p, err := NewJSONPathPrinter(templateValue)
if err != nil {
return nil, true, fmt.Errorf("error parsing jsonpath %s, %v\n", templateValue, err)
}
allowMissingKeys := true
if f.AllowMissingKeys != nil {
allowMissingKeys = *f.AllowMissingKeys
}
p.AllowMissingKeys(allowMissingKeys)
return p, true, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *JSONPathPrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when --output=jsonpath, --output=jsonpath-file.")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewJSONPathPrintFlags returns flags associated with
// --template printing, with default values set.
func NewJSONPathPrintFlags(templateValue string, allowMissingKeys bool) *JSONPathPrintFlags {
return &JSONPathPrintFlags{
TemplateArgument: &templateValue,
AllowMissingKeys: &allowMissingKeys,
}
}

View File

@ -0,0 +1,212 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers_test
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/printers"
)
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 := printers.JSONPathPrintFlags{
TemplateArgument: templateArg,
}
p, matched, err := printFlags.ToPrinter(tc.outputFormat)
if tc.expectNoMatch {
if matched {
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
}
return
}
if !matched {
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 := printers.JSONPathPrintFlags{
TemplateArgument: &tc.templateArg,
AllowMissingKeys: tc.allowMissingKeys,
}
outputFormat := "jsonpath"
p, matched, err := printFlags.ToPrinter(outputFormat)
if !matched {
t.Fatalf("expected to match template printer for output format %q", outputFormat)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
if len(tc.expectedError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}

View File

@ -0,0 +1,70 @@
/*
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"
// 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) ToPrinter(outputFormat string) (ResourcePrinter, bool, error) {
if p, match, err := f.JSONPathPrintFlags.ToPrinter(outputFormat); match {
return p, match, 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,
}
}

View File

@ -18,7 +18,6 @@ package printers
import (
"fmt"
"io/ioutil"
"k8s.io/apimachinery/pkg/runtime"
)
@ -57,57 +56,28 @@ func GetStandardPrinter(typer runtime.ObjectTyper, encoder runtime.Encoder, deco
printer = namePrinter
case "template", "go-template":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("template format specified but no template given")
case "template", "go-template", "jsonpath", "templatefile", "go-template-file", "jsonpath-file":
// TODO: construct and bind this separately (at the command level)
kubeTemplateFlags := KubeTemplatePrintFlags{
GoTemplatePrintFlags: &GoTemplatePrintFlags{
AllowMissingKeys: &allowMissingTemplateKeys,
TemplateArgument: &formatArgument,
},
JSONPathPrintFlags: &JSONPathPrintFlags{
AllowMissingKeys: &allowMissingTemplateKeys,
TemplateArgument: &formatArgument,
},
}
templatePrinter, err := NewTemplatePrinter([]byte(formatArgument))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", formatArgument, err)
}
templatePrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = templatePrinter
case "templatefile", "go-template-file":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("templatefile format specified but no template file given")
kubeTemplatePrinter, matched, err := kubeTemplateFlags.ToPrinter(format)
if !matched {
return nil, fmt.Errorf("unable to match a template printer to handle current print options")
}
data, err := ioutil.ReadFile(formatArgument)
if err != nil {
return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
return nil, err
}
templatePrinter, err := NewTemplatePrinter(data)
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
}
templatePrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = templatePrinter
case "jsonpath":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("jsonpath template format specified but no template given")
}
jsonpathPrinter, err := NewJSONPathPrinter(formatArgument)
if err != nil {
return nil, fmt.Errorf("error parsing jsonpath %s, %v\n", formatArgument, err)
}
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter
case "jsonpath-file":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("jsonpath file format specified but no template file given")
}
data, err := ioutil.ReadFile(formatArgument)
if err != nil {
return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
}
jsonpathPrinter, err := NewJSONPathPrinter(string(data))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
}
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter
printer = kubeTemplatePrinter
case "custom-columns", "custom-columns-file":
customColumnsFlags := &CustomColumnsPrintFlags{

View File

@ -26,13 +26,13 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
// TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type TemplatePrinter struct {
// GoTemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type GoTemplatePrinter struct {
rawTemplate string
template *template.Template
}
func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) {
func NewGoTemplatePrinter(tmpl []byte) (*GoTemplatePrinter, error) {
t, err := template.New("output").
Funcs(template.FuncMap{
"exists": exists,
@ -42,14 +42,14 @@ func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) {
if err != nil {
return nil, err
}
return &TemplatePrinter{
return &GoTemplatePrinter{
rawTemplate: string(tmpl),
template: t,
}, nil
}
// AllowMissingKeys tells the template engine if missing keys are allowed.
func (p *TemplatePrinter) AllowMissingKeys(allow bool) {
func (p *GoTemplatePrinter) AllowMissingKeys(allow bool) {
if allow {
p.template.Option("missingkey=default")
} else {
@ -57,12 +57,12 @@ func (p *TemplatePrinter) AllowMissingKeys(allow bool) {
}
}
func (p *TemplatePrinter) AfterPrint(w io.Writer, res string) error {
func (p *GoTemplatePrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj formats the obj with the Go Template.
func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
func (p *GoTemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
var data []byte
var err error
data, err = json.Marshal(obj)
@ -88,17 +88,17 @@ func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
}
// TODO: implement HandledResources()
func (p *TemplatePrinter) HandledResources() []string {
func (p *GoTemplatePrinter) HandledResources() []string {
return []string{}
}
func (p *TemplatePrinter) IsGeneric() bool {
func (p *GoTemplatePrinter) IsGeneric() bool {
return true
}
// safeExecute tries to execute the template, but catches panics and returns an error
// should the template engine panic.
func (p *TemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {
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

View File

@ -0,0 +1,123 @@
/*
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"
)
// 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
}
// 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, bool, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, false, fmt.Errorf("missing --template argument")
}
templateValue := ""
// templates are logically optional for specifying a format.
// this allows a user to specify a template format value
// as --output=go-template=
supportedFormats := map[string]bool{
"template": true,
"go-template": true,
"go-template-file": true,
"templatefile": true,
}
if f.TemplateArgument == nil || len(*f.TemplateArgument) == 0 {
for format := range supportedFormats {
format = format + "="
if strings.HasPrefix(templateFormat, format) {
templateValue = templateFormat[len(format):]
templateFormat = format[:len(format)-1]
break
}
}
} else {
templateValue = *f.TemplateArgument
}
if _, supportedFormat := supportedFormats[templateFormat]; !supportedFormat {
return nil, false, nil
}
if len(templateValue) == 0 {
return nil, true, 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, true, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
}
templateValue = string(data)
}
p, err := NewGoTemplatePrinter([]byte(templateValue))
if err != nil {
return nil, true, fmt.Errorf("error parsing template %s, %v\n", templateValue, err)
}
allowMissingKeys := true
if f.AllowMissingKeys != nil {
allowMissingKeys = *f.AllowMissingKeys
}
p.AllowMissingKeys(allowMissingKeys)
return p, true, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *GoTemplatePrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewGoTemplatePrintFlags returns flags associated with
// --template printing, with default values set.
func NewGoTemplatePrintFlags() *GoTemplatePrintFlags {
allowMissingKeysPtr := true
templateValuePtr := ""
return &GoTemplatePrintFlags{
TemplateArgument: &templateValuePtr,
AllowMissingKeys: &allowMissingKeysPtr,
}
}

View File

@ -0,0 +1,206 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers_test
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/printers"
)
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 := printers.GoTemplatePrintFlags{
TemplateArgument: templateArg,
}
p, matched, err := printFlags.ToPrinter(tc.outputFormat)
if tc.expectNoMatch {
if matched {
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
}
return
}
if !matched {
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 := printers.GoTemplatePrintFlags{
TemplateArgument: &tc.templateArg,
AllowMissingKeys: tc.allowMissingKeys,
}
outputFormat := "template"
p, matched, err := printFlags.ToPrinter(outputFormat)
if !matched {
t.Fatalf("expected to match template printer for output format %q", outputFormat)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
if len(tc.expectedError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}

View File

@ -53,7 +53,7 @@ func TestTemplate(t *testing.T) {
for name, test := range testCase {
buffer := &bytes.Buffer{}
p, err := NewTemplatePrinter([]byte(test.template))
p, err := NewGoTemplatePrinter([]byte(test.template))
if err != nil {
if test.expectErr == nil {
t.Errorf("[%s]expected success but got:\n %v\n", name, err)