feat: Add template func hasField (#1754)

Signed-off-by: Lehman, Alex <alex.lehman@gtri.gatech.edu>
This commit is contained in:
Alex Lehman 2023-04-21 09:34:06 -04:00 committed by GitHub
parent a42bac6fcc
commit b2b332e8b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 0 deletions

View File

@ -261,6 +261,8 @@ Which would produce output like:
Syft also includes a vast array of utility templating functions from [sprig](http://masterminds.github.io/sprig/) apart from the default Golang [text/template](https://pkg.go.dev/text/template#hdr-Functions) to allow users to customize the output format. Syft also includes a vast array of utility templating functions from [sprig](http://masterminds.github.io/sprig/) apart from the default Golang [text/template](https://pkg.go.dev/text/template#hdr-Functions) to allow users to customize the output format.
Lastly, Syft has custom templating functions defined in `./syft/format/template/encoder.go` to help parse the passed-in JSON structs.
## Multiple outputs ## Multiple outputs
Syft can also output _multiple_ files in differing formats by appending Syft can also output _multiple_ files in differing formats by appending

View File

@ -235,6 +235,37 @@ func DirectoryInput(t testing.TB) sbom.SBOM {
} }
} }
func DirectoryInputWithAuthorField(t testing.TB) sbom.SBOM {
catalog := newDirectoryCatalogWithAuthorField()
src, err := source.NewFromDirectory("/some/path")
assert.NoError(t, err)
return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
LinuxDistribution: &linux.Release{
PrettyName: "debian",
Name: "debian",
ID: "debian",
IDLike: []string{"like!"},
Version: "1.2.3",
VersionID: "1.2.3",
},
},
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: "syft",
Version: "v0.42.0-bogus",
// the application configuration should be persisted here, however, we do not want to import
// the application configuration in this package (it's reserved only for ingestion by the cmd package)
Configuration: map[string]string{
"config-key": "config-value",
},
},
}
}
func newDirectoryCatalog() *pkg.Catalog { func newDirectoryCatalog() *pkg.Catalog {
catalog := pkg.NewCatalog() catalog := pkg.NewCatalog()
@ -286,6 +317,58 @@ func newDirectoryCatalog() *pkg.Catalog {
return catalog return catalog
} }
func newDirectoryCatalogWithAuthorField() *pkg.Catalog {
catalog := pkg.NewCatalog()
// populate catalog with test data
catalog.Add(pkg.Package{
Name: "package-1",
Version: "1.0.1",
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Locations: source.NewLocationSet(
source.NewLocation("/some/path/pkg1"),
),
Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.PythonPackageMetadata{
Name: "package-1",
Version: "1.0.1",
Author: "test-author",
Files: []pkg.PythonFileRecord{
{
Path: "/some/path/pkg1/dependencies/foo",
},
},
},
PURL: "a-purl-2", // intentionally a bad pURL for test fixtures
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
Name: "package-2",
Version: "2.0.1",
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
Locations: source.NewLocationSet(
source.NewLocation("/some/path/pkg1"),
),
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "package-2",
Version: "2.0.1",
},
PURL: "pkg:deb/debian/package-2@2.0.1",
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
return catalog
}
//nolint:gosec //nolint:gosec
func AddSampleFileRelationships(s *sbom.SBOM) { func AddSampleFileRelationships(s *sbom.SBOM) {
catalog := s.Artifacts.PackageCatalog.Sorted() catalog := s.Artifacts.PackageCatalog.Sorted()

View File

@ -45,5 +45,11 @@ var funcMap = func() template.FuncMap {
return 0 return 0
} }
// Checks if a field is defined
f["hasField"] = func(obj interface{}, field string) bool {
t := reflect.TypeOf(obj)
_, ok := t.FieldByName(field)
return ok
}
return f return f
}() }()

View File

@ -24,6 +24,19 @@ func TestFormatWithOption(t *testing.T) {
} }
func TestFormatWithOptionAndHasField(t *testing.T) {
f := OutputFormat{}
f.SetTemplatePath("test-fixtures/csv-hasField.template")
testutils.AssertEncoderAgainstGoldenSnapshot(t,
f,
testutils.DirectoryInputWithAuthorField(t),
*updateTmpl,
false,
)
}
func TestFormatWithoutOptions(t *testing.T) { func TestFormatWithoutOptions(t *testing.T) {
f := Format() f := Format()
err := f.Encode(nil, testutils.DirectoryInput(t)) err := f.Encode(nil, testutils.DirectoryInput(t))

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