github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/template/encoder.go (about) 1 package template 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "reflect" 11 "text/template" 12 13 "github.com/Masterminds/sprig/v3" 14 "github.com/mitchellh/go-homedir" 15 16 "github.com/anchore/syft/syft/format/syftjson" 17 "github.com/anchore/syft/syft/sbom" 18 ) 19 20 const ID sbom.FormatID = "template" 21 22 type EncoderConfig struct { 23 TemplatePath string 24 Legacy bool 25 syftjson.EncoderConfig 26 } 27 28 type encoder struct { 29 cfg EncoderConfig 30 funcMap template.FuncMap 31 } 32 33 func NewFormatEncoder(cfg EncoderConfig) (sbom.FormatEncoder, error) { 34 // TODO: revisit this... should no template file be an error or simply render an empty result? or render the json output? 35 // Note: do not check for the existence of the template file here, as the default encoder cannot provide one. 36 f := sprig.HermeticTxtFuncMap() 37 f["getLastIndex"] = func(collection interface{}) int { 38 if v := reflect.ValueOf(collection); v.Kind() == reflect.Slice { 39 return v.Len() - 1 40 } 41 42 return 0 43 } 44 // Checks if a field is defined 45 f["hasField"] = func(obj interface{}, field string) bool { 46 t := reflect.TypeOf(obj) 47 _, ok := t.FieldByName(field) 48 return ok 49 } 50 51 return encoder{ 52 cfg: cfg, 53 // These are custom functions available to template authors. 54 funcMap: f, 55 }, nil 56 } 57 58 func DefaultEncoderConfig() EncoderConfig { 59 return EncoderConfig{ 60 EncoderConfig: syftjson.DefaultEncoderConfig(), 61 } 62 } 63 64 func (e encoder) ID() sbom.FormatID { 65 return ID 66 } 67 68 func (e encoder) Aliases() []string { 69 return []string{} 70 } 71 72 func (e encoder) Version() string { 73 return sbom.AnyVersion 74 } 75 76 func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error { 77 if e.cfg.TemplatePath == "" { 78 return errors.New("no template file provided") 79 } 80 81 templatePath, err := homedir.Expand(e.cfg.TemplatePath) 82 if err != nil { 83 return fmt.Errorf("unable to expand path %s", e.cfg.TemplatePath) 84 } 85 86 templateContents, err := os.ReadFile(templatePath) 87 if err != nil { 88 return fmt.Errorf("unable to get template content: %w", err) 89 } 90 91 tmpl, err := template.New(templatePath).Funcs(e.funcMap).Parse(string(templateContents)) 92 if err != nil { 93 return fmt.Errorf("unable to parse template: %w", err) 94 } 95 96 var doc any 97 if e.cfg.Legacy { 98 doc = syftjson.ToFormatModel(s, e.cfg.EncoderConfig) 99 } else { 100 enc, err := syftjson.NewFormatEncoderWithConfig(e.cfg.EncoderConfig) 101 if err != nil { 102 return fmt.Errorf("unable to create json encoder: %w", err) 103 } 104 105 var buff bytes.Buffer 106 if err = enc.Encode(&buff, s); err != nil { 107 return fmt.Errorf("unable to encode json: %w", err) 108 } 109 110 deserializedDoc := make(map[string]any) 111 112 if err = json.Unmarshal(buff.Bytes(), &deserializedDoc); err != nil { 113 return fmt.Errorf("unable to unmarshal json for template: %w", err) 114 } 115 116 doc = deserializedDoc 117 } 118 119 return tmpl.Execute(writer, doc) 120 }