@@ -4,9 +4,11 @@ import (
44 "bytes"
55 "context"
66 "encoding/base64"
7+ "fmt"
78 "io"
89 "io/fs"
910 "os"
11+ "strings"
1012 "text/template"
1113
1214 "dario.cat/mergo"
@@ -45,6 +47,13 @@ type Function struct {
4547 fsys fs.FS
4648}
4749
50+ type YamlErrorContext struct {
51+ RelLine int
52+ AbsLine int
53+ Message string
54+ Context string
55+ }
56+
4857const (
4958 annotationKeyCompositionResourceName = "gotemplating.fn.crossplane.io/composition-resource-name"
5059 annotationKeyReady = "gotemplating.fn.crossplane.io/ready"
@@ -106,14 +115,34 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
106115
107116 // Parse the rendered manifests.
108117 var objs []* unstructured.Unstructured
109- decoder := yaml .NewYAMLOrJSONDecoder (bytes .NewBufferString (buf .String ()), 1024 )
118+ data := buf .String ()
119+ decoder := yaml .NewYAMLOrJSONDecoder (bytes .NewBufferString (data ), 1024 )
120+
121+ lines := strings .Split (data , "\n " )
122+ startLine := moveToNextDoc (lines , 1 )
123+ docIndex := 0
124+
110125 for {
111126 u := & unstructured.Unstructured {}
112127 if err := decoder .Decode (& u ); err != nil {
113128 if err == io .EOF {
114129 break
115130 }
116- response .Fatal (rsp , errors .Wrap (err , "cannot decode manifest" ))
131+
132+ var newErr error
133+ yamlErr := getYamlErrorContextFromErr (err , startLine , lines )
134+ if yamlErr == (YamlErrorContext {}) {
135+ newErr = err
136+ } else {
137+ context := strings .TrimSpace (yamlErr .Context )
138+ if len (context ) > 80 {
139+ context = context [:80 ] + "..."
140+ }
141+
142+ newErr = fmt .Errorf ("error converting YAML to JSON: yaml: line %d (document %d, line %d) near: '%s': %s" , yamlErr .AbsLine , docIndex + 1 , yamlErr .RelLine , context , yamlErr .Message )
143+ }
144+
145+ response .Fatal (rsp , errors .Wrap (newErr , "cannot decode manifest" ))
117146 return rsp , nil
118147 }
119148
@@ -131,6 +160,9 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
131160 }
132161
133162 objs = append (objs , u )
163+
164+ startLine = moveToNextDoc (lines , startLine )
165+ docIndex ++
134166 }
135167
136168 // Get the desired composite resource from the request.
@@ -328,3 +360,38 @@ func safeApplyTemplateOptions(templ *template.Template, options []string) (err e
328360 templ .Option (options ... )
329361 return nil
330362}
363+
364+ func moveToNextDoc (lines []string , startLine int ) int {
365+ for i := startLine ; i <= len (lines ); i ++ {
366+ if strings .TrimSpace (lines [i - 1 ]) == "---" && i > startLine {
367+ return i
368+ }
369+ }
370+ return startLine
371+ }
372+
373+ func getYamlErrorContextFromErr (err error , startLine int , lines []string ) YamlErrorContext {
374+ var relLine int
375+ n , scanErr := fmt .Sscanf (err .Error (), "error converting YAML to JSON: yaml: line %d:" , & relLine )
376+ var errMsg string
377+ if scanErr == nil && n == 1 {
378+ // Extract the rest of the error message after the matched prefix.
379+ prefix := fmt .Sprintf ("error converting YAML to JSON: yaml: line %d:" , relLine )
380+ errStr := err .Error ()
381+ if idx := strings .Index (errStr , prefix ); idx != - 1 {
382+ errMsg = strings .TrimSpace (errStr [idx + len (prefix ):])
383+ }
384+ }
385+ if scanErr == nil && n == 1 {
386+ absLine := startLine + relLine
387+ if absLine - 1 < len (lines ) && absLine - 1 >= 0 {
388+ return YamlErrorContext {
389+ RelLine : relLine ,
390+ AbsLine : absLine ,
391+ Message : errMsg ,
392+ Context : lines [absLine - 1 ],
393+ }
394+ }
395+ }
396+ return YamlErrorContext {}
397+ }
0 commit comments