github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/template/encoder.go (about)

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