Skip to content

Commit

Permalink
feat: Add template func hasField (#1754)
Browse files Browse the repository at this point in the history
Signed-off-by: Lehman, Alex <alex.lehman@gtri.gatech.edu>
  • Loading branch information
jlehman9 authored Apr 21, 2023
1 parent a42bac6 commit b2b332e
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Lastly, Syft has custom templating functions defined in `./syft/format/template/encoder.go` to help parse the passed-in JSON structs.

## Multiple outputs

Syft can also output _multiple_ files in differing formats by appending
Expand Down
83 changes: 83 additions & 0 deletions syft/formats/internal/testutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
catalog := pkg.NewCatalog()

Expand Down Expand Up @@ -286,6 +317,58 @@ func newDirectoryCatalog() *pkg.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
func AddSampleFileRelationships(s *sbom.SBOM) {
catalog := s.Artifacts.PackageCatalog.Sorted()
Expand Down
6 changes: 6 additions & 0 deletions syft/formats/template/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,11 @@ var funcMap = func() template.FuncMap {

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
}()
13 changes: 13 additions & 0 deletions syft/formats/template/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
f := Format()
err := f.Encode(nil, testutils.DirectoryInput(t))
Expand Down
4 changes: 4 additions & 0 deletions syft/formats/template/test-fixtures/csv-hasField.template
Original file line number Diff line number Diff line change
@@ -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}}
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit b2b332e

Please # to comment.