Skip to content

Commit 6dfcfee

Browse files
Merge pull request #55 from snyk/chore/pass-target-file-out-of-plugin
chore: pass target file and exclusions out of plugin
2 parents 280a3bd + c46066c commit 6dfcfee

File tree

8 files changed

+76
-43
lines changed

8 files changed

+76
-43
lines changed

internal/mocks/uvclient.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
package mocks
22

3-
import "github.com/rs/zerolog"
3+
import (
4+
"github.com/rs/zerolog"
5+
6+
"github.com/snyk/cli-extension-dep-graph/internal/uv"
7+
scaplugin "github.com/snyk/cli-extension-dep-graph/pkg/sca_plugin"
8+
)
49

510
// MockUVClient is a mock implementation of UVClient for testing
611
type MockUVClient struct {
7-
ExportSBOMFunc func(inputDir string) ([]byte, error)
12+
ExportSBOMFunc func(inputDir string) (*scaplugin.Finding, error)
813
ShouldExportSBOMFunc func(inputDir string, logger *zerolog.Logger) bool
914
}
1015

11-
func (m *MockUVClient) ExportSBOM(inputDir string) ([]byte, error) {
16+
func (m *MockUVClient) ExportSBOM(inputDir string) (*scaplugin.Finding, error) {
1217
if m.ExportSBOMFunc != nil {
1318
return m.ExportSBOMFunc(inputDir)
1419
}
15-
return []byte(`{"mock":"sbom"}`), nil
20+
return &scaplugin.Finding{
21+
Sbom: []byte(`{"mock":"sbom"}`),
22+
FileExclusions: []string{},
23+
NormalisedTargetFile: uv.UvLockFileName,
24+
}, nil
1625
}
1726

1827
func (m *MockUVClient) ShouldExportSBOM(inputDir string, logger *zerolog.Logger) bool {
@@ -21,3 +30,5 @@ func (m *MockUVClient) ShouldExportSBOM(inputDir string, logger *zerolog.Logger)
2130
}
2231
return true
2332
}
33+
34+
var _ uv.Client = (*MockUVClient)(nil)

internal/uv/uv.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import (
88
"github.com/rs/zerolog"
99
)
1010

11-
const UvLockFileName = "uv.lock"
11+
const (
12+
UvLockFileName = "uv.lock"
13+
RequirementsTxtFileName = "requirements.txt"
14+
PyprojectTomlFileName = "pyproject.toml"
15+
)
1216

1317
// This is copied from cli-extension-os-flows. We could export from here via GAF config and re-use if this duplication
1418
// becomes a problem, but this duplication is only temporary.

internal/uv/uv_plugin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ func (p Plugin) BuildFindingsFromDir(inputDir string, _ scaplugin.Options, logge
2222
return []scaplugin.Finding{}, nil
2323
}
2424

25-
sbomOutput, err := p.client.ExportSBOM(inputDir)
25+
finding, err := p.client.ExportSBOM(inputDir)
2626
if err != nil {
2727
return []scaplugin.Finding{}, fmt.Errorf("failed to export SBOM using uv: %w", err)
2828
}
29-
return []scaplugin.Finding{{Sbom: sbomOutput, FilesProcessed: []string{}}}, nil
29+
return []scaplugin.Finding{*finding}, nil
3030
}

internal/uv/uvclient.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99
"strings"
1010

1111
"github.com/rs/zerolog"
12+
scaplugin "github.com/snyk/cli-extension-dep-graph/pkg/sca_plugin"
1213
)
1314

1415
type Client interface {
15-
ExportSBOM(inputDir string) ([]byte, error)
16+
ExportSBOM(inputDir string) (*scaplugin.Finding, error)
1617
ShouldExportSBOM(inputDir string, logger *zerolog.Logger) bool
1718
}
1819

@@ -40,7 +41,7 @@ func NewUvClientWithExecutor(uvBinary string, executor cmdExecutor) Client {
4041
}
4142

4243
// exportSBOM exports an SBOM in CycloneDX format using uv.
43-
func (c client) ExportSBOM(inputDir string) ([]byte, error) {
44+
func (c client) ExportSBOM(inputDir string) (*scaplugin.Finding, error) {
4445
output, err := c.executor.Execute(c.uvBinary, inputDir, "export", "--format", "cyclonedx1.5", "--frozen", "--preview")
4546
if err != nil {
4647
return nil, fmt.Errorf("failed to execute uv export: %w", err)
@@ -50,7 +51,17 @@ func (c client) ExportSBOM(inputDir string) ([]byte, error) {
5051
return nil, err
5152
}
5253

53-
return output, nil
54+
return &scaplugin.Finding{
55+
Sbom: output,
56+
FileExclusions: []string{
57+
// TODO(uv): uncomment when we are able to pass these to the CLI correctly. Currently the
58+
// `--exclude` flag does not accept paths, it only accepts file or dir names, which does not
59+
// work for our use case.
60+
// path.Join(inputDir, uv.RequirementsTxtFileName),
61+
// path.Join(inputDir, uv.PyprojectTomlFileName),
62+
},
63+
NormalisedTargetFile: UvLockFileName,
64+
}, nil
5465
}
5566

5667
// Verifies that the SBOM is valid JSON and has a root component.

internal/uv/uvclient_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestUVClient_ExportSBOM_Success(t *testing.T) {
4545
result, err := client.ExportSBOM("/test/dir")
4646

4747
assert.NoError(t, err)
48-
assert.JSONEq(t, validSBOM, string(result))
48+
assert.JSONEq(t, validSBOM, string(result.Sbom))
4949
}
5050

5151
func TestUVClient_ExportSBOM_Error(t *testing.T) {

pkg/depgraph/sbom_resolution.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func handleSBOMResolutionDI(
8181
// Convert SBOMs to workflow.Data
8282
workflowData := []workflow.Data{}
8383
for _, f := range findings { // Could be parallelised in future
84-
data, err := sbomToWorkflowData(f.Sbom, snykClient, logger, remoteRepoURL)
84+
data, err := sbomToWorkflowData(f, snykClient, logger, remoteRepoURL)
8585
if err != nil {
8686
return nil, fmt.Errorf("error converting SBOM: %w", err)
8787
}
@@ -106,7 +106,7 @@ func handleSBOMResolutionDI(
106106
func getExclusionsFromFindings(findings []scaplugin.Finding) []string {
107107
exclusions := []string{}
108108
for _, f := range findings {
109-
exclusions = append(exclusions, f.FilesProcessed...)
109+
exclusions = append(exclusions, f.FileExclusions...)
110110
}
111111
return exclusions
112112
}
@@ -150,8 +150,8 @@ func executeLegacyWorkflow(
150150
return nil, fmt.Errorf("error handling legacy workflow: %w", err)
151151
}
152152

153-
func sbomToWorkflowData(sbomOutput []byte, snykClient *snykclient.SnykClient, logger *zerolog.Logger, remoteRepoURL string) ([]workflow.Data, error) {
154-
sbomReader := bytes.NewReader(sbomOutput)
153+
func sbomToWorkflowData(finding scaplugin.Finding, snykClient *snykclient.SnykClient, logger *zerolog.Logger, remoteRepoURL string) ([]workflow.Data, error) {
154+
sbomReader := bytes.NewReader(finding.Sbom)
155155

156156
scans, warnings, err := snykClient.SBOMConvert(context.Background(), logger, sbomReader, remoteRepoURL)
157157
if err != nil {
@@ -160,7 +160,7 @@ func sbomToWorkflowData(sbomOutput []byte, snykClient *snykclient.SnykClient, lo
160160

161161
logger.Printf("Successfully converted SBOM, warning(s): %d\n", len(warnings))
162162

163-
depGraphsData, err := extractDepGraphsFromScans(scans)
163+
depGraphsData, err := extractDepGraphsFromScans(scans, finding.NormalisedTargetFile)
164164
if err != nil {
165165
return nil, fmt.Errorf("failed to extract depgraphs from scan results: %w", err)
166166
}
@@ -171,7 +171,7 @@ func sbomToWorkflowData(sbomOutput []byte, snykClient *snykclient.SnykClient, lo
171171
return depGraphsData, nil
172172
}
173173

174-
func extractDepGraphsFromScans(scans []*snykclient.ScanResult) ([]workflow.Data, error) {
174+
func extractDepGraphsFromScans(scans []*snykclient.ScanResult, targetFile string) ([]workflow.Data, error) {
175175
var depGraphList []workflow.Data
176176

177177
for _, scan := range scans {
@@ -189,8 +189,8 @@ func extractDepGraphsFromScans(scans []*snykclient.ScanResult) ([]workflow.Data,
189189
// Create workflow data with the depgraph
190190
data := workflow.NewData(DataTypeID, contentTypeJSON, depGraphBytes)
191191

192-
data.SetMetaData(contentLocationKey, "uv.lock")
193-
data.SetMetaData(MetaKeyNormalisedTargetFile, "uv.lock")
192+
data.SetMetaData(contentLocationKey, targetFile)
193+
data.SetMetaData(MetaKeyNormalisedTargetFile, targetFile)
194194

195195
if scan.Identity.Type != "" {
196196
data.SetMetaData(MetaKeyTargetFileFromPlugin, scan.Identity.Type)

pkg/depgraph/sbom_resolution_test.go

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,10 @@ func Test_callback_SBOMResolution(t *testing.T) {
131131
ctx.config.Set(configuration.API_URL, mockSBOMService.URL)
132132

133133
mockUVClient := &mocks.MockUVClient{
134-
ExportSBOMFunc: func(_ string) ([]byte, error) {
135-
return []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`), nil
134+
ExportSBOMFunc: func(_ string) (*scaplugin.Finding, error) {
135+
return &scaplugin.Finding{
136+
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`),
137+
}, nil
136138
},
137139
}
138140

@@ -159,7 +161,7 @@ func Test_callback_SBOMResolution(t *testing.T) {
159161
resolutionHandler := NewCalledResolutionHandlerFunc(nil, nil)
160162

161163
mockUVClient := &mocks.MockUVClient{
162-
ExportSBOMFunc: func(_ string) ([]byte, error) {
164+
ExportSBOMFunc: func(_ string) (*scaplugin.Finding, error) {
163165
return nil, fmt.Errorf("uv command failed")
164166
},
165167
}
@@ -187,8 +189,10 @@ func Test_callback_SBOMResolution(t *testing.T) {
187189
ctx.config.Set(configuration.API_URL, mockSBOMService.URL)
188190

189191
mockUVClient := &mocks.MockUVClient{
190-
ExportSBOMFunc: func(_ string) ([]byte, error) {
191-
return []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`), nil
192+
ExportSBOMFunc: func(_ string) (*scaplugin.Finding, error) {
193+
return &scaplugin.Finding{
194+
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`),
195+
}, nil
192196
},
193197
}
194198

@@ -238,8 +242,8 @@ func Test_callback_SBOMResolution(t *testing.T) {
238242
// Create mock plugin that returns two findings
239243
mockPlugin := &mockScaPlugin{
240244
findings: []scaplugin.Finding{
241-
{Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`), FilesProcessed: []string{}},
242-
{Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[{"name":"test"}]}`), FilesProcessed: []string{}},
245+
{Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`), FileExclusions: []string{}},
246+
{Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[{"name":"test"}]}`), FileExclusions: []string{}},
243247
},
244248
}
245249

@@ -268,8 +272,10 @@ func Test_callback_SBOMResolution(t *testing.T) {
268272
ctx.config.Set(configuration.API_URL, mockSBOMService.URL)
269273

270274
mockUVClient := &mocks.MockUVClient{
271-
ExportSBOMFunc: func(_ string) ([]byte, error) {
272-
return []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`), nil
275+
ExportSBOMFunc: func(_ string) (*scaplugin.Finding, error) {
276+
return &scaplugin.Finding{
277+
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`),
278+
}, nil
273279
},
274280
}
275281

@@ -291,19 +297,19 @@ func Test_callback_SBOMResolution(t *testing.T) {
291297
t.Run("handleSBOMResolution with FlagAllProjects", func(t *testing.T) {
292298
finding1 := scaplugin.Finding{
293299
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`),
294-
FilesProcessed: []string{"uv.lock", "pyproject.toml"},
300+
FileExclusions: []string{"uv.lock", "pyproject.toml"},
295301
}
296302
finding2 := scaplugin.Finding{
297303
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[{"name":"test"}]}`),
298-
FilesProcessed: []string{"requirements.txt", "setup.py"},
304+
FileExclusions: []string{"requirements.txt", "setup.py"},
299305
}
300306
finding3 := scaplugin.Finding{
301307
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[{"name":"someFinding"}]}`),
302-
FilesProcessed: []string{"package.json"},
308+
FileExclusions: []string{"package.json"},
303309
}
304310
finding4 := scaplugin.Finding{
305311
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[{"name":"anotherFinding"}]}`),
306-
FilesProcessed: []string{"go.mod"},
312+
FileExclusions: []string{"go.mod"},
307313
}
308314

309315
tc := []struct {
@@ -514,7 +520,7 @@ func Test_callback_SBOMResolution(t *testing.T) {
514520
findings: []scaplugin.Finding{
515521
{
516522
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`),
517-
FilesProcessed: []string{"uv.lock"},
523+
FileExclusions: []string{"uv.lock"},
518524
},
519525
},
520526
}
@@ -584,7 +590,7 @@ func Test_callback_SBOMResolution(t *testing.T) {
584590
findings: []scaplugin.Finding{
585591
{
586592
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","components":[]}`),
587-
FilesProcessed: []string{"uv.lock"},
593+
FileExclusions: []string{"uv.lock"},
588594
},
589595
},
590596
}
@@ -626,7 +632,7 @@ func Test_getExclusionsFromFindings(t *testing.T) {
626632
findings: []scaplugin.Finding{
627633
{
628634
Sbom: []byte(`{"bomFormat":"CycloneDX"}`),
629-
FilesProcessed: []string{},
635+
FileExclusions: []string{},
630636
},
631637
},
632638
expected: []string{},
@@ -636,7 +642,7 @@ func Test_getExclusionsFromFindings(t *testing.T) {
636642
findings: []scaplugin.Finding{
637643
{
638644
Sbom: []byte(`{"bomFormat":"CycloneDX"}`),
639-
FilesProcessed: []string{"file1.py", "file2.py"},
645+
FileExclusions: []string{"file1.py", "file2.py"},
640646
},
641647
},
642648
expected: []string{"file1.py", "file2.py"},
@@ -646,11 +652,11 @@ func Test_getExclusionsFromFindings(t *testing.T) {
646652
findings: []scaplugin.Finding{
647653
{
648654
Sbom: []byte(`{"bomFormat":"CycloneDX"}`),
649-
FilesProcessed: []string{"file1.py", "file2.py"},
655+
FileExclusions: []string{"file1.py", "file2.py"},
650656
},
651657
{
652658
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5"}`),
653-
FilesProcessed: []string{"file3.py", "file4.py", "file5.py"},
659+
FileExclusions: []string{"file3.py", "file4.py", "file5.py"},
654660
},
655661
},
656662
expected: []string{"file1.py", "file2.py", "file3.py", "file4.py", "file5.py"},
@@ -660,15 +666,15 @@ func Test_getExclusionsFromFindings(t *testing.T) {
660666
findings: []scaplugin.Finding{
661667
{
662668
Sbom: []byte(`{"bomFormat":"CycloneDX"}`),
663-
FilesProcessed: []string{},
669+
FileExclusions: []string{},
664670
},
665671
{
666672
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5"}`),
667-
FilesProcessed: []string{"file1.py"},
673+
FileExclusions: []string{"file1.py"},
668674
},
669675
{
670676
Sbom: []byte(`{"bomFormat":"CycloneDX","specVersion":"1.6"}`),
671-
FilesProcessed: []string{"file2.py", "file3.py"},
677+
FileExclusions: []string{"file2.py", "file3.py"},
672678
},
673679
},
674680
expected: []string{"file1.py", "file2.py", "file3.py"},

pkg/sca_plugin/interface.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import "github.com/rs/zerolog"
55
type Options struct{}
66

77
type Finding struct {
8-
Sbom Sbom
9-
FilesProcessed []string
8+
Sbom Sbom // The raw SBOM bytes
9+
FileExclusions []string // Paths for files that other plugins should ignore
10+
NormalisedTargetFile string // The target file name without any qualifiers, e.g. `uv.lock` (and not `dir/uv.lock`)
1011
}
1112

1213
type Sbom []byte

0 commit comments

Comments
 (0)