Use the json schema as input for templating (#2542)

* use the json schema as input for templating

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix cli tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2024-01-25 09:00:35 -05:00 committed by GitHub
parent 11c0b1c234
commit a32b8d7fc6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 100 additions and 13 deletions

View File

@ -555,6 +555,7 @@ select-catalogers: []
format:
# default value for all formats that support the "pretty" option (default is unset)
# SYFT_FORMAT_PRETTY env var
pretty:
# all syft-json format options
@ -562,6 +563,7 @@ format:
# include space indention and newlines (inherits default value from 'format.pretty' or 'false' if parent is unset)
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
# SYFT_FORMAT_JSON_PRETTY env var
pretty: false
# transform any syft-json output to conform to an approximation of the v11.0.1 schema. This includes:
@ -571,21 +573,30 @@ format:
# that output might not strictly be json schema v11 compliant, however, for consumers that require time to port
# over to the final syft 1.0 json output this option can be used to ease the transition.
#
# Note: long term support for this option is not guaranteed.
# Note: long term support for this option is not guaranteed (it may change or break at any time).
# SYFT_FORMAT_JSON_LEGACY env var
legacy: false
# all template format options
template:
# path to the template file to use when rendering the output with the `template` output format.
# Note that all template paths are based on the current syft-json schema.
# SYFT_TEMPLATE_PATH env var / -t flag
# SYFT_FORMAT_TEMPLATE_PATH env var / -t flag
path: ""
# if true, uses the go structs for the syft-json format for templating.
# if false, uses the syft-json output for templating (which follows the syft JSON schema exactly).
#
# Note: long term support for this option is not guaranteed (it may change or break at any time).
# SYFT_FORMAT_TEMPLATE_LEGACY env var
legacy: false
# all spdx-json format options
spdx-json:
# include space indention and newlines
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
# SYFT_FORMAT_SPDX_JSON_PRETTY env var
pretty: false
# all cyclonedx-json format options
@ -593,6 +604,7 @@ format:
# include space indention and newlines
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
# SYFT_FORMAT_CYCLONEDX_JSON_PRETTY env var
pretty: false
# all cyclonedx-xml format options
@ -600,6 +612,7 @@ format:
# include space indention
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
# SYFT_FORMAT_CYCLONEDX_XML_PRETTY env var
pretty: false

View File

@ -10,6 +10,7 @@ var _ clio.FlagAdder = (*FormatTemplate)(nil)
type FormatTemplate struct {
Enabled bool `yaml:"-" json:"-" mapstructure:"-"`
Path string `yaml:"path" json:"path" mapstructure:"path"` // -t template file to use for output
Legacy bool `yaml:"legacy" json:"legacy" mapstructure:"legacy"`
}
func DefaultFormatTemplate() FormatTemplate {
@ -28,5 +29,6 @@ func (o *FormatTemplate) AddFlags(flags clio.FlagSet) {
func (o FormatTemplate) config() template.EncoderConfig {
return template.EncoderConfig{
TemplatePath: o.Path,
Legacy: o.Legacy,
}
}

View File

@ -72,9 +72,7 @@ func (o EncodersConfig) Encoders() ([]sbom.FormatEncoder, error) {
}
func (o EncodersConfig) templateEncoders() ([]sbom.FormatEncoder, error) {
enc, err := template.NewFormatEncoder(template.EncoderConfig{
TemplatePath: o.Template.TemplatePath,
})
enc, err := template.NewFormatEncoder(o.Template)
return []sbom.FormatEncoder{enc}, err
}

View File

@ -1,6 +1,8 @@
package template
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@ -19,6 +21,7 @@ const ID sbom.FormatID = "template"
type EncoderConfig struct {
TemplatePath string
Legacy bool
syftjson.EncoderConfig
}
@ -90,6 +93,28 @@ func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
return fmt.Errorf("unable to parse template: %w", err)
}
doc := syftjson.ToFormatModel(s, e.cfg.EncoderConfig)
var doc any
if e.cfg.Legacy {
doc = syftjson.ToFormatModel(s, e.cfg.EncoderConfig)
} else {
enc, err := syftjson.NewFormatEncoderWithConfig(e.cfg.EncoderConfig)
if err != nil {
return fmt.Errorf("unable to create json encoder: %w", err)
}
var buff bytes.Buffer
if err = enc.Encode(&buff, s); err != nil {
return fmt.Errorf("unable to encode json: %w", err)
}
deserializedDoc := make(map[string]any)
if err = json.Unmarshal(buff.Bytes(), &deserializedDoc); err != nil {
return fmt.Errorf("unable to unmarshal json for template: %w", err)
}
doc = deserializedDoc
}
return tmpl.Execute(writer, doc)
}

View File

@ -12,6 +12,42 @@ import (
var updateSnapshot = flag.Bool("update-template", false, "update the *.golden files for json encoders")
func TestFormatWithOption_Legacy(t *testing.T) {
f, err := NewFormatEncoder(EncoderConfig{
TemplatePath: "test-fixtures/legacy/csv.template",
Legacy: true,
})
require.NoError(t, err)
testutil.AssertEncoderAgainstGoldenSnapshot(t,
testutil.EncoderSnapshotTestConfig{
Subject: testutil.DirectoryInput(t, t.TempDir()),
Format: f,
UpdateSnapshot: *updateSnapshot,
PersistRedactionsInSnapshot: true,
IsJSON: false,
},
)
}
func TestFormatWithOptionAndHasField_Legacy(t *testing.T) {
f, err := NewFormatEncoder(EncoderConfig{
TemplatePath: "test-fixtures/legacy/csv-hasField.template",
Legacy: true,
})
require.NoError(t, err)
testutil.AssertEncoderAgainstGoldenSnapshot(t,
testutil.EncoderSnapshotTestConfig{
Subject: testutil.DirectoryInputWithAuthorField(t),
Format: f,
UpdateSnapshot: *updateSnapshot,
PersistRedactionsInSnapshot: true,
IsJSON: false,
},
)
}
func TestFormatWithOption(t *testing.T) {
f, err := NewFormatEncoder(EncoderConfig{
TemplatePath: "test-fixtures/csv.template",
@ -44,7 +80,6 @@ func TestFormatWithOptionAndHasField(t *testing.T) {
IsJSON: false,
},
)
}
func TestFormatWithoutOptions(t *testing.T) {

View File

@ -1,4 +1,4 @@
"Package","Version Installed","Found by","Author"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}","{{ if hasField .Metadata "Author" }}{{.Metadata.Author}}{{ else }}NO AUTHOR SUPPLIED{{end}}"
{{- range .artifacts}}
"{{.name}}","{{.version}}","{{.foundBy}}","{{ if index .metadata "author" }}{{.metadata.author}}{{ else }}NO AUTHOR SUPPLIED{{end}}"
{{- end}}

View File

@ -1,4 +1,4 @@
"Package","Version Installed", "Found by"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
{{- range .artifacts}}
"{{.name}}","{{.version}}","{{.foundBy}}"
{{- end}}

View File

@ -0,0 +1,4 @@
"Package","Version Installed","Found by","Author"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}","{{ if hasField .Metadata "Author" }}{{.Metadata.Author}}{{ else }}NO AUTHOR SUPPLIED{{end}}"
{{- end}}

View File

@ -0,0 +1,4 @@
"Package","Version Installed", "Found by"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
{{- end}}

View File

@ -0,0 +1,3 @@
"Package","Version Installed","Found by","Author"
"package-1","1.0.1","the-cataloger-1","test-author"
"package-2","2.0.1","the-cataloger-2","NO AUTHOR SUPPLIED"

View File

@ -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"

View File

@ -1,4 +1,4 @@
"Package","Version Installed", "Found by"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
{{- range .artifacts}}
"{{.name}}","{{.version}}","{{.foundBy}}"
{{- end}}