mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 23:16:30 +01:00
add template output (#1051)
* add template output Signed-off-by: Jonas Xavier <jonasx@anchore.com> * remove dead code Signed-off-by: Jonas Xavier <jonasx@anchore.com> * fix template cli flag Signed-off-by: Jonas Xavier <jonasx@anchore.com> * implement template's own format type Signed-off-by: Jonas Xavier <jonasx@anchore.com> * simpler code Signed-off-by: Jonas Xavier <jonasx@anchore.com> * fix readme link to Go template Signed-off-by: Jonas Xavier <jonasx@anchore.com> * feedback changes Signed-off-by: Jonas Xavier <jonasx@anchore.com> * simpler func signature patter Signed-off-by: Jonas Xavier <jonasx@anchore.com> * nit Signed-off-by: Jonas Xavier <jonasx@anchore.com> * fix linter error Signed-off-by: Jonas Xavier <jonasx@anchore.com>
This commit is contained in:
@@ -38,6 +38,7 @@ type Application struct {
|
||||
// -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
|
||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func makeTemplateExecutor(templateFilePath string) (*template.Template, error) {
|
||||
if templateFilePath == "" {
|
||||
return nil, errors.New("no template file: please provide a template path")
|
||||
}
|
||||
|
||||
expandedPathToTemplateFile, err := homedir.Expand(templateFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to expand path %s", templateFilePath)
|
||||
}
|
||||
|
||||
templateContents, err := os.ReadFile(expandedPathToTemplateFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get template content: %w", err)
|
||||
}
|
||||
|
||||
templateName := expandedPathToTemplateFile
|
||||
tmpl, err := template.New(templateName).Funcs(funcMap).Parse(string(templateContents))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse template: %w", err)
|
||||
}
|
||||
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
// These are custom functions available to template authors.
|
||||
var funcMap = func() template.FuncMap {
|
||||
f := sprig.HermeticTxtFuncMap()
|
||||
f["getLastIndex"] = func(collection interface{}) int {
|
||||
if v := reflect.ValueOf(collection); v.Kind() == reflect.Slice {
|
||||
return v.Len() - 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
return f
|
||||
}()
|
||||
@@ -0,0 +1,29 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var updateTmpl = flag.Bool("update-tmpl", false, "update the *.golden files for json encoders")
|
||||
|
||||
func TestFormatWithOption(t *testing.T) {
|
||||
f := OutputFormat{}
|
||||
f.SetTemplatePath("test-fixtures/csv.template")
|
||||
|
||||
testutils.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
f,
|
||||
testutils.DirectoryInput(t),
|
||||
*updateTmpl,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func TestFormatWithoutOptions(t *testing.T) {
|
||||
f := Format()
|
||||
err := f.Encode(nil, testutils.DirectoryInput(t))
|
||||
assert.ErrorContains(t, err, "no template file: please provide a template path")
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/syftjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const ID sbom.FormatID = "template"
|
||||
|
||||
func Format() sbom.Format {
|
||||
return OutputFormat{}
|
||||
}
|
||||
|
||||
// implementation of sbom.Format interface
|
||||
// to make use of format options
|
||||
type OutputFormat struct {
|
||||
templateFilePath string
|
||||
}
|
||||
|
||||
func (f OutputFormat) ID() sbom.FormatID {
|
||||
return ID
|
||||
}
|
||||
|
||||
func (f OutputFormat) Decode(reader io.Reader) (*sbom.SBOM, error) {
|
||||
return nil, sbom.ErrDecodingNotSupported
|
||||
}
|
||||
|
||||
func (f OutputFormat) Encode(output io.Writer, s sbom.SBOM) error {
|
||||
tmpl, err := makeTemplateExecutor(f.templateFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
doc := syftjson.ToFormatModel(s)
|
||||
return tmpl.Execute(output, doc)
|
||||
}
|
||||
|
||||
func (f OutputFormat) Validate(reader io.Reader) error {
|
||||
return sbom.ErrValidationNotSupported
|
||||
}
|
||||
|
||||
// SetTemplatePath sets path for template file
|
||||
func (f *OutputFormat) SetTemplatePath(filePath string) {
|
||||
f.templateFilePath = filePath
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
"Package","Version Installed", "Found by"
|
||||
{{- range .Artifacts}}
|
||||
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
|
||||
{{- end}}
|
||||
@@ -0,0 +1,3 @@
|
||||
"Package","Version Installed", "Found by"
|
||||
"package-1","1.0.1","the-cataloger-1"
|
||||
"package-2","2.0.1","the-cataloger-2"
|
||||
Reference in New Issue
Block a user