Commit 9ef96309 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #39486 from ncdc/allow-missing-keys-in-templates

Automatic merge from submit-queue (batch tested with PRs 39486, 37288, 39477, 39455, 39542) 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). Fixes #37991 @kubernetes/sig-cli-misc @kubernetes/api-reviewers @smarterclayton @fabianofranz @liggitt @pwittrock
parents b84c9b4b 80c5cd8b
......@@ -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() {
......
......@@ -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
......
......@@ -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
}
......
......@@ -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
}
......
......@@ -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
}
......
......@@ -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 {
......@@ -2512,6 +2518,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
}
......
......@@ -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)
......@@ -1794,3 +1794,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())
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment