diff --git a/pkg/kubecfg/resource_printer.go b/pkg/kubecfg/resource_printer.go index 01837bfe904..619ea2e985a 100644 --- a/pkg/kubecfg/resource_printer.go +++ b/pkg/kubecfg/resource_printer.go @@ -327,7 +327,8 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er // TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template. type TemplatePrinter struct { - template *template.Template + rawTemplate string + template *template.Template } func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) { @@ -335,17 +336,27 @@ func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) { if err != nil { return nil, err } - return &TemplatePrinter{t}, nil + return &TemplatePrinter{string(tmpl), t}, nil } // Print parses the data as JSON, and re-formats it with the Go Template. func (t *TemplatePrinter) Print(data []byte, w io.Writer) error { - obj := map[string]interface{}{} - err := json.Unmarshal(data, &obj) + out := map[string]interface{}{} + err := json.Unmarshal(data, &out) if err != nil { return err } - return t.template.Execute(w, obj) + if err := t.template.Execute(w, out); err != nil { + // It is way easier to debug this stuff when it shows up in + // stdout instead of just stdin. So in addition to returning + // a nice error, also print useful stuff with the writer. + fmt.Fprintf(w, "Error executing template: %v\n", err) + fmt.Fprintf(w, "template was:\n%v\n", t.rawTemplate) + fmt.Fprintf(w, "raw data was:\n%v\n", string(data)) + fmt.Fprintf(w, "object given to template engine was:\n%+v\n", out) + return fmt.Errorf("error executing template '%v': '%v'\n----data----\n%#v\n", t.rawTemplate, err, out) + } + return nil } // PrintObj formats the obj with the Go Template. diff --git a/pkg/kubecfg/resource_printer_test.go b/pkg/kubecfg/resource_printer_test.go index ef5abd96048..c57c59b5fa9 100644 --- a/pkg/kubecfg/resource_printer_test.go +++ b/pkg/kubecfg/resource_printer_test.go @@ -177,3 +177,20 @@ func TestTemplateEmitsVersionedObjects(t *testing.T) { t.Errorf("Expected %v, got %v", e, a) } } + +func TestTemplatePanic(t *testing.T) { + tmpl := `{{and ((index .currentState.info "update-demo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}` + // kind is always blank in memory and set on the wire + printer, err := NewTemplatePrinter([]byte(tmpl)) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + buffer := &bytes.Buffer{} + err = printer.PrintObj(&api.Pod{}, buffer) + if err == nil { + t.Fatalf("expected that template to crash") + } + if buffer.String() == "" { + t.Errorf("no debugging info was printed") + } +} diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 0f8aa3ba888..cfeb994f996 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -417,7 +417,14 @@ func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error { return err } if err = p.template.Execute(w, out); err != nil { - return fmt.Errorf("error executing template '%v': '%v'\n----data----\n%#v\n", p.rawTemplate, err, out) + // It is way easier to debug this stuff when it shows up in + // stdout instead of just stdin. So in addition to returning + // a nice error, also print useful stuff with the writer. + fmt.Fprintf(w, "Error executing template: %v\n", err) + fmt.Fprintf(w, "template was:\n\t%v\n", p.rawTemplate) + fmt.Fprintf(w, "raw data was:\n\t%v\n", string(data)) + fmt.Fprintf(w, "object given to template engine was:\n\t%+v\n", out) + return fmt.Errorf("error executing template '%v': '%v'\n----data----\n%+v\n", p.rawTemplate, err, out) } return nil } diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index a79852775ca..5e829e6569e 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -290,6 +290,23 @@ func TestTemplateEmitsVersionedObjects(t *testing.T) { } } +func TestTemplatePanic(t *testing.T) { + tmpl := `{{and ((index .currentState.info "update-demo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}` + // kind is always blank in memory and set on the wire + printer, err := NewTemplatePrinter([]byte(tmpl), testapi.Version(), api.Scheme) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + buffer := &bytes.Buffer{} + err = printer.PrintObj(&api.Pod{}, buffer) + if err == nil { + t.Fatalf("expected that template to crash") + } + if buffer.String() == "" { + t.Errorf("no debugging info was printed") + } +} + func TestPrinters(t *testing.T) { om := func(name string) api.ObjectMeta { return api.ObjectMeta{Name: name} } templatePrinter, err := NewTemplatePrinter([]byte("{{.name}}"), testapi.Version(), api.Scheme)