mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
wire through template PrintFlags
This commit is contained in:
parent
90c09c75d6
commit
ec672ca279
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
118
pkg/printers/jsonpath_flags.go
Normal file
118
pkg/printers/jsonpath_flags.go
Normal 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,
|
||||
}
|
||||
}
|
212
pkg/printers/jsonpath_flags_test.go
Normal file
212
pkg/printers/jsonpath_flags_test.go
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_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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
70
pkg/printers/kube_template_flags.go
Normal file
70
pkg/printers/kube_template_flags.go
Normal 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,
|
||||
}
|
||||
}
|
@ -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{
|
||||
|
@ -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
|
||||
|
123
pkg/printers/template_flags.go
Normal file
123
pkg/printers/template_flags.go
Normal 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,
|
||||
}
|
||||
}
|
206
pkg/printers/template_flags_test.go
Normal file
206
pkg/printers/template_flags_test.go
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_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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user