Allow missing keys in templates by default

Switch to allowing missing keys in jsonpath templates by default.

Add support for allowing/disallowing missing keys in go templates
(default=allow).

Add --allow-missing-template-keys flag to control this behavior
(default=true / allow missing keys).
pull/6/head
Andy Goldstein 2017-01-05 15:29:44 -05:00
parent 5503e5e6be
commit 80c5cd8b88
7 changed files with 104 additions and 14 deletions

View File

@ -1045,6 +1045,33 @@ run_kubectl_get_tests() {
kube::test::if_has_string "${output_message}" "/apis/batch/v1/namespaces/default/jobs 200 OK"
kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/deployments 200 OK"
kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/replicasets 200 OK"
### Test --allow-missing-template-keys
# Pre-condition: no POD exists
create_and_use_new_namespace
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
# Command
kubectl create -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml "${kube_flags[@]}"
# Post-condition: valid-pod POD is created
kubectl get "${kube_flags[@]}" pods -o json
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:'
## check --allow-missing-template-keys defaults to true for jsonpath templates
kubectl get "${kube_flags[@]}" pod valid-pod -o jsonpath='{.missing}'
## check --allow-missing-template-keys defaults to true for go templates
kubectl get "${kube_flags[@]}" pod valid-pod -o go-template='{{.missing}}'
## check --allow-missing-template-keys=false results in an error for a missing key with jsonpath
output_message=$(! kubectl get pod valid-pod --allow-missing-template-keys=false -o jsonpath='{.missing}' "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'error executing jsonpath "{.missing}": missing is not found'
## check --allow-missing-template-keys=false results in an error for a missing key with go
output_message=$(! kubectl get pod valid-pod --allow-missing-template-keys=false -o go-template='{{.missing}}' "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'error executing template "{{.missing}}": template: output:1:2: executing "output" at <.missing>: map has no entry for key "missing"'
# cleanup
kubectl delete pods valid-pod "${kube_flags[@]}"
}
run_kubectl_request_timeout_tests() {

View File

@ -7,6 +7,7 @@ advertised-address
algorithm-provider
all-namespaces
allocate-node-cidrs
allow-missing-template-keys
allow-privileged
allowed-not-ready-nodes
anonymous-auth

View File

@ -91,7 +91,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, args []string, out i
return err
}
printer, _, err := kubectl.GetPrinter("json", "", false)
printer, _, err := kubectl.GetPrinter("json", "", false, true)
if err != nil {
return err
}

View File

@ -146,7 +146,7 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
}
}
o.encoder = f.JSONEncoder()
o.printer, _, err = kubectl.GetPrinter(outputFormat, templateFile, false)
o.printer, _, err = kubectl.GetPrinter(outputFormat, templateFile, false, cmdutil.GetFlagBool(cmd, "allow-missing-template-keys"))
if err != nil {
return err
}

View File

@ -49,6 +49,7 @@ func AddOutputFlagsForMutation(cmd *cobra.Command) {
// AddOutputFlags adds output related flags to a command.
func AddOutputFlags(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
cmd.Flags().Bool("allow-missing-template-keys", true, "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.")
}
// AddNoHeadersFlags adds no-headers flags to a command.
@ -126,7 +127,13 @@ func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error
}
}
printer, generic, err := kubectl.GetPrinter(outputFormat, templateFile, GetFlagBool(cmd, "no-headers"))
// this function may be invoked by a command that did not call AddPrinterFlags first, so we need
// to be safe about how we access the allow-missing-template-keys flag
allowMissingTemplateKeys := false
if cmd.Flags().Lookup("allow-missing-template-keys") != nil {
allowMissingTemplateKeys = GetFlagBool(cmd, "allow-missing-template-keys")
}
printer, generic, err := kubectl.GetPrinter(outputFormat, templateFile, GetFlagBool(cmd, "no-headers"), allowMissingTemplateKeys)
if err != nil {
return nil, generic, err
}

View File

@ -71,7 +71,7 @@ const (
// is agnostic to schema versions, so you must send arguments to PrintObj in the
// version you wish them to be shown using a VersionedPrinter (typically when
// generic is true).
func GetPrinter(format, formatArgument string, noHeaders bool) (ResourcePrinter, bool, error) {
func GetPrinter(format, formatArgument string, noHeaders, allowMissingTemplateKeys bool) (ResourcePrinter, bool, error) {
var printer ResourcePrinter
switch format {
case "json":
@ -88,11 +88,12 @@ func GetPrinter(format, formatArgument string, noHeaders bool) (ResourcePrinter,
if len(formatArgument) == 0 {
return nil, false, fmt.Errorf("template format specified but no template given")
}
var err error
printer, err = NewTemplatePrinter([]byte(formatArgument))
templatePrinter, err := NewTemplatePrinter([]byte(formatArgument))
if err != nil {
return nil, false, 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, false, fmt.Errorf("templatefile format specified but no template file given")
@ -101,19 +102,22 @@ func GetPrinter(format, formatArgument string, noHeaders bool) (ResourcePrinter,
if err != nil {
return nil, false, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
}
printer, err = NewTemplatePrinter(data)
templatePrinter, err := NewTemplatePrinter(data)
if err != nil {
return nil, false, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
}
templatePrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = templatePrinter
case "jsonpath":
if len(formatArgument) == 0 {
return nil, false, fmt.Errorf("jsonpath template format specified but no template given")
}
var err error
printer, err = NewJSONPathPrinter(formatArgument)
jsonpathPrinter, err := NewJSONPathPrinter(formatArgument)
if err != nil {
return nil, false, fmt.Errorf("error parsing jsonpath %s, %v\n", formatArgument, err)
}
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter
case "jsonpath-file":
if len(formatArgument) == 0 {
return nil, false, fmt.Errorf("jsonpath file format specified but no template file file given")
@ -122,10 +126,12 @@ func GetPrinter(format, formatArgument string, noHeaders bool) (ResourcePrinter,
if err != nil {
return nil, false, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
}
printer, err = NewJSONPathPrinter(string(data))
jsonpathPrinter, err := NewJSONPathPrinter(string(data))
if err != nil {
return nil, false, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
}
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter
case "custom-columns":
var err error
if printer, err = NewCustomColumnsPrinterFromSpec(formatArgument, api.Codecs.UniversalDecoder(), noHeaders); err != nil {
@ -2527,6 +2533,15 @@ func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) {
}, nil
}
// AllowMissingKeys tells the template engine if missing keys are allowed.
func (p *TemplatePrinter) AllowMissingKeys(allow bool) {
if allow {
p.template.Option("missingkey=default")
} else {
p.template.Option("missingkey=error")
}
}
func (p *TemplatePrinter) AfterPrint(w io.Writer, res string) error {
return nil
}

View File

@ -78,7 +78,7 @@ func TestVersionedPrinter(t *testing.T) {
}
func TestPrintDefault(t *testing.T) {
printer, found, err := GetPrinter("", "", false)
printer, found, err := GetPrinter("", "", false, false)
if err != nil {
t.Fatalf("unexpected error: %#v", err)
}
@ -133,7 +133,7 @@ func TestPrinter(t *testing.T) {
}
for _, test := range printerTests {
buf := bytes.NewBuffer([]byte{})
printer, generic, err := GetPrinter(test.Format, test.FormatArgument, false)
printer, generic, err := GetPrinter(test.Format, test.FormatArgument, false, true)
if err != nil {
t.Errorf("in %s, unexpected error: %#v", test.Name, err)
}
@ -163,7 +163,7 @@ func TestBadPrinter(t *testing.T) {
{"bad jsonpath", "jsonpath", "{.Name", fmt.Errorf("error parsing jsonpath {.Name, unclosed action\n")},
}
for _, test := range badPrinterTests {
_, _, err := GetPrinter(test.Format, test.FormatArgument, false)
_, _, err := GetPrinter(test.Format, test.FormatArgument, false, false)
if err == nil || err.Error() != test.Error.Error() {
t.Errorf("in %s, expect %s, got %s", test.Name, test.Error, err)
}
@ -356,7 +356,7 @@ func TestNamePrinter(t *testing.T) {
},
"pod/foo\npod/bar\n"},
}
printer, _, _ := GetPrinter("name", "", false)
printer, _, _ := GetPrinter("name", "", false, false)
for name, item := range tests {
buff := &bytes.Buffer{}
err := printer.PrintObj(item.obj, buff)
@ -1706,3 +1706,43 @@ func TestPrintPodDisruptionBudget(t *testing.T) {
buf.Reset()
}
}
func TestAllowMissingKeys(t *testing.T) {
tests := []struct {
Name string
AllowMissingTemplateKeys bool
Format string
Template string
Input runtime.Object
Expect string
Error string
}{
{"test template, allow missing keys", true, "template", "{{.blarg}}", &api.Pod{}, "<no value>", ""},
{"test template, strict", false, "template", "{{.blarg}}", &api.Pod{}, "", `error executing template "{{.blarg}}": template: output:1:2: executing "output" at <.blarg>: map has no entry for key "blarg"`},
{"test jsonpath, allow missing keys", true, "jsonpath", "{.blarg}", &api.Pod{}, "", ""},
{"test jsonpath, strict", false, "jsonpath", "{.blarg}", &api.Pod{}, "", "error executing jsonpath \"{.blarg}\": blarg is not found\n"},
}
for _, test := range tests {
buf := bytes.NewBuffer([]byte{})
printer, _, err := GetPrinter(test.Format, test.Template, false, test.AllowMissingTemplateKeys)
if err != nil {
t.Errorf("in %s, unexpected error: %#v", test.Name, err)
}
err = printer.PrintObj(test.Input, buf)
if len(test.Error) == 0 && err != nil {
t.Errorf("in %s, unexpected error: %v", test.Name, err)
continue
}
if len(test.Error) > 0 {
if err == nil {
t.Errorf("in %s, expected to get error: %v", test.Name, test.Error)
} else if e, a := test.Error, err.Error(); e != a {
t.Errorf("in %s, expected error %q, got %q", test.Name, e, a)
}
continue
}
if buf.String() != test.Expect {
t.Errorf("in %s, expect %q, got %q", test.Name, test.Expect, buf.String())
}
}
}