Skip to content

Commit

Permalink
Merge pull request #123 from paketo-buildpacks/syft-imprv
Browse files Browse the repository at this point in the history
Use syft's multiple output formats support
  • Loading branch information
Daniel Mikusa authored Feb 8, 2022
2 parents 4b68a8f + fb30bc7 commit 16b3969
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 16b3969

Please sign in to comment.