mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
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
This commit is contained in:
commit
9ef9630976
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user