mirror of https://github.com/k3s-io/k3s
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
parent
5503e5e6be
commit
80c5cd8b88
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue