Skip to content

Commit

Permalink
Use syft's multiple output formats support
Browse files Browse the repository at this point in the history
Syft now has support to write multiple output formats from a single scan. This means we no longer need to loop and run syft multiple times, so this will be more efficient.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
  • Loading branch information
Daniel Mikusa committed Feb 7, 2022
1 parent 2b25eae commit fb30bc7
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 55 deletions.
33 changes: 7 additions & 26 deletions sbom/sbom.go
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/buildpacks/libcnb"
"github.com/mitchellh/hashstructure/v2"
Expand Down Expand Up @@ -139,38 +138,20 @@ func (b SyftCLISBOMScanner) ScanLaunch(scanDir string, formats ...libcnb.SBOMFor
}

func (b SyftCLISBOMScanner) scan(sbomPathCreator func(libcnb.SBOMFormat) string, scanDir string, formats ...libcnb.SBOMFormat) error {
// syft doesn't presently support outputting multiple formats at once
// to workaround this we are running syft multiple times
// when syft supports multiple output formats or conversion between formats, this method should change
for _, format := range formats {
sbomLocation := sbomPathCreator(format)
args := []string{"packages", "-q"}

if err := b.runSyft(sbomLocation, scanDir, format); err != nil {
return fmt.Errorf("unable to run syft\n%w", err)
}
for _, format := range formats {
args = append(args, "-o", fmt.Sprintf("%s=%s", SBOMFormatToSyftOutputFormat(format), sbomPathCreator(format)))
}

return nil
}
args = append(args, fmt.Sprintf("dir:%s", scanDir))

func (b SyftCLISBOMScanner) runSyft(sbomOutputPath string, scanDir string, format libcnb.SBOMFormat) error {
writer, err := os.Create(sbomOutputPath)
if err != nil {
return fmt.Errorf("unable to open output BOM file %s\n%w", sbomOutputPath, err)
}
defer writer.Close()

err = b.Executor.Execute(effect.Execution{
return b.Executor.Execute(effect.Execution{
Command: "syft",
Args: []string{"packages", "-q", "-o", SBOMFormatToSyftOutputFormat(format), fmt.Sprintf("dir:%s", scanDir)},
Stdout: writer,
Args: args,
Stdout: b.Logger.TerminalErrorWriter(),
Stderr: b.Logger.TerminalErrorWriter(),
})
if err != nil {
return fmt.Errorf("unable to run syft on directory %s\n%w", scanDir, err)
}

return nil
}

// SBOMFormatToSyftOutputFormat converts a libcnb.SBOMFormat to the syft matching syft output format string
Expand Down
63 changes: 34 additions & 29 deletions sbom/sbom_test.go
Expand Up @@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"github.com/buildpacks/libcnb"
Expand Down Expand Up @@ -62,7 +63,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
return e.Command == "syft" &&
len(e.Args) == 5 &&
e.Args[3] == "json" &&
strings.HasPrefix(e.Args[3], "json=") &&
e.Args[4] == "dir:something"
})).Run(func(args mock.Arguments) {
Expect(ioutil.WriteFile(outputPath, []byte("succeed1"), 0644)).To(Succeed())
Expand All @@ -85,7 +86,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
return e.Command == "syft" &&
len(e.Args) == 5 &&
e.Args[3] == "json" &&
strings.HasPrefix(e.Args[3], "json=") &&
e.Args[4] == "dir:something"
})).Run(func(args mock.Arguments) {
Expect(ioutil.WriteFile(outputPath, []byte("succeed2"), 0644)).To(Succeed())
Expand All @@ -104,35 +105,39 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
Expect(string(result)).To(Equal("succeed2"))
})

it("runs syft thrice, once per format", func() {
outputPaths := map[libcnb.SBOMFormat]string{
libcnb.SPDXJSON: layers.LaunchSBOMPath(libcnb.SPDXJSON),
libcnb.SyftJSON: layers.LaunchSBOMPath(libcnb.SyftJSON),
libcnb.CycloneDXJSON: layers.LaunchSBOMPath(libcnb.CycloneDXJSON),
}
it("runs syft once for all three formats", func() {
executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
return e.Command == "syft" &&
len(e.Args) == 9 &&
strings.HasPrefix(e.Args[3], sbom.SBOMFormatToSyftOutputFormat(libcnb.CycloneDXJSON)) &&
strings.HasPrefix(e.Args[5], sbom.SBOMFormatToSyftOutputFormat(libcnb.SyftJSON)) &&
strings.HasPrefix(e.Args[7], sbom.SBOMFormatToSyftOutputFormat(libcnb.SPDXJSON)) &&
e.Args[8] == "dir:something"
})).Run(func(args mock.Arguments) {
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.CycloneDXJSON), []byte("succeed1"), 0644)).To(Succeed())
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.SyftJSON), []byte("succeed2"), 0644)).To(Succeed())
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.SPDXJSON), []byte("succeed3"), 0644)).To(Succeed())
}).Return(nil)

for format, outputPath := range outputPaths {
executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
return e.Command == "syft" &&
len(e.Args) == 5 &&
e.Args[3] == sbom.SBOMFormatToSyftOutputFormat(format) &&
e.Args[4] == "dir:something"
})).Run(func(args mock.Arguments) {
Expect(ioutil.WriteFile(outputPath, []byte("succeed3"), 0644)).To(Succeed())
}).Return(nil)

scanner := sbom.SyftCLISBOMScanner{
Executor: &executor,
Layers: layers,
Logger: bard.NewLogger(io.Discard),
}

Expect(scanner.ScanLaunch("something", format)).To(Succeed())

result, err := ioutil.ReadFile(outputPath)
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).To(Equal("succeed3"))
scanner := sbom.SyftCLISBOMScanner{
Executor: &executor,
Layers: layers,
Logger: bard.NewLogger(io.Discard),
}

Expect(scanner.ScanLaunch("something", libcnb.CycloneDXJSON, libcnb.SyftJSON, libcnb.SPDXJSON)).To(Succeed())

result, err := ioutil.ReadFile(layers.LaunchSBOMPath(libcnb.CycloneDXJSON))
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).To(Equal("succeed1"))

result, err = ioutil.ReadFile(layers.LaunchSBOMPath(libcnb.SyftJSON))
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).To(Equal("succeed2"))

result, err = ioutil.ReadFile(layers.LaunchSBOMPath(libcnb.SPDXJSON))
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).To(Equal("succeed3"))
})

it("writes out a manual BOM entry", func() {
Expand Down

0 comments on commit fb30bc7

Please sign in to comment.