From b2b332e8b2b66af0905e98b54ebd713a922be1a8 Mon Sep 17 00:00:00 2001 From: Alex Lehman <117683089+jlehman9@users.noreply.github.com> Date: Fri, 21 Apr 2023 09:34:06 -0400 Subject: [PATCH] feat: Add template func `hasField` (#1754) Signed-off-by: Lehman, Alex --- README.md | 2 + syft/formats/internal/testutils/utils.go | 83 +++++++++++++++++++ syft/formats/template/encoder.go | 6 ++ syft/formats/template/encoder_test.go | 13 +++ .../test-fixtures/csv-hasField.template | 4 + .../TestFormatWithOptionAndHasField.golden | 3 + 6 files changed, 111 insertions(+) create mode 100644 syft/formats/template/test-fixtures/csv-hasField.template create mode 100644 syft/formats/template/test-fixtures/snapshot/TestFormatWithOptionAndHasField.golden diff --git a/README.md b/README.md index f00f0979d035..948a38ae805d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/syft/formats/internal/testutils/utils.go b/syft/formats/internal/testutils/utils.go index f214c4f07f34..ef03bf195431 100644 --- a/syft/formats/internal/testutils/utils.go +++ b/syft/formats/internal/testutils/utils.go @@ -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() @@ -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() diff --git a/syft/formats/template/encoder.go b/syft/formats/template/encoder.go index 8ef95b73a7f4..6cdce172881f 100644 --- a/syft/formats/template/encoder.go +++ b/syft/formats/template/encoder.go @@ -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 }() diff --git a/syft/formats/template/encoder_test.go b/syft/formats/template/encoder_test.go index 34eb6c79a5ad..aed6dca526a1 100644 --- a/syft/formats/template/encoder_test.go +++ b/syft/formats/template/encoder_test.go @@ -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)) diff --git a/syft/formats/template/test-fixtures/csv-hasField.template b/syft/formats/template/test-fixtures/csv-hasField.template new file mode 100644 index 000000000000..ad5ddb77fe20 --- /dev/null +++ b/syft/formats/template/test-fixtures/csv-hasField.template @@ -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}} \ No newline at end of file diff --git a/syft/formats/template/test-fixtures/snapshot/TestFormatWithOptionAndHasField.golden b/syft/formats/template/test-fixtures/snapshot/TestFormatWithOptionAndHasField.golden new file mode 100644 index 000000000000..89c9fd10b469 --- /dev/null +++ b/syft/formats/template/test-fixtures/snapshot/TestFormatWithOptionAndHasField.golden @@ -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" \ No newline at end of file