Skip to content

Commit 1009858

Browse files
committed
fix: add empty dep-graph when there are no dependencies
1 parent 2356867 commit 1009858

File tree

3 files changed

+71
-23
lines changed

3 files changed

+71
-23
lines changed

internal/uv/uvclient.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ func (c client) ExportSBOM(inputDir string) (*scaplugin.Finding, error) {
4848
return nil, fmt.Errorf("failed to execute uv export: %w", err)
4949
}
5050

51-
if err := validateSBOM(output); err != nil {
51+
metadata, err := validateSBOM(output)
52+
if err != nil {
5253
return nil, err
5354
}
5455

5556
return &scaplugin.Finding{
56-
Sbom: output,
57+
Sbom: output,
58+
Metadata: *metadata,
5759
FileExclusions: []string{
5860
path.Join(inputDir, RequirementsTxtFileName),
5961
path.Join(inputDir, PyprojectTomlFileName),
@@ -63,22 +65,30 @@ func (c client) ExportSBOM(inputDir string) (*scaplugin.Finding, error) {
6365
}
6466

6567
// Verifies that the SBOM is valid JSON and has a root component.
66-
func validateSBOM(sbomData []byte) error {
68+
func validateSBOM(sbomData []byte) (*scaplugin.Metadata, error) {
6769
var sbom struct {
6870
Metadata struct {
69-
Component json.RawMessage `json:"component"`
71+
// TODO: test this with and without component
72+
Component *struct {
73+
Name string `json:"name"`
74+
Version string `json:"version"`
75+
} `json:"component"`
7076
} `json:"metadata"`
7177
}
7278

7379
if err := json.Unmarshal(sbomData, &sbom); err != nil {
74-
return fmt.Errorf("failed to parse SBOM: %w", err)
80+
return nil, fmt.Errorf("failed to parse SBOM: %w", err)
7581
}
7682

77-
if len(sbom.Metadata.Component) == 0 {
78-
return fmt.Errorf("SBOM missing root component at metadata.component - uv project may be missing a root package")
83+
if sbom.Metadata.Component == nil {
84+
return nil, fmt.Errorf("SBOM missing root component at metadata.component - uv project may be missing a root package")
7985
}
8086

81-
return nil
87+
return &scaplugin.Metadata{
88+
PackageManager: "pip",
89+
Name: sbom.Metadata.Component.Name,
90+
Version: sbom.Metadata.Component.Version,
91+
}, nil
8292
}
8393

8494
func (c *client) ShouldExportSBOM(inputDir string, logger *zerolog.Logger) bool {

pkg/depgraph/sbom_resolution.go

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/rs/zerolog"
11+
"github.com/snyk/cli-extension-dep-graph/internal/depgraph"
1112
"github.com/snyk/cli-extension-dep-graph/internal/snykclient"
1213
"github.com/snyk/cli-extension-dep-graph/internal/uv"
1314
scaplugin "github.com/snyk/cli-extension-dep-graph/pkg/sca_plugin"
@@ -81,7 +82,7 @@ func handleSBOMResolutionDI(
8182
// Convert SBOMs to workflow.Data
8283
workflowData := []workflow.Data{}
8384
for _, f := range findings { // Could be parallelised in future
84-
data, err := sbomToWorkflowData(f, snykClient, logger, remoteRepoURL)
85+
data, err := sbomToWorkflowData(&f, snykClient, logger, remoteRepoURL)
8586
if err != nil {
8687
return nil, fmt.Errorf("error converting SBOM: %w", err)
8788
}
@@ -150,7 +151,7 @@ func executeLegacyWorkflow(
150151
return nil, fmt.Errorf("error handling legacy workflow: %w", err)
151152
}
152153

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

156157
scans, warnings, err := snykClient.SBOMConvert(context.Background(), logger, sbomReader, remoteRepoURL)
@@ -166,11 +167,32 @@ func sbomToWorkflowData(finding scaplugin.Finding, snykClient *snykclient.SnykCl
166167
}
167168

168169
if len(depGraphsData) == 0 {
169-
return nil, fmt.Errorf("no dependency graphs found in SBOM conversion response")
170+
depGraph, err := emptyDepGraph(finding)
171+
if err != nil {
172+
return nil, fmt.Errorf("failed to create empty depgraph: %w", err)
173+
}
174+
175+
data, err := workflowDataFromDepGraph(depGraph, finding.TargetFile, "")
176+
if err != nil {
177+
return nil, fmt.Errorf("failed to create workflow data: %w", err)
178+
}
179+
depGraphsData = append(depGraphsData, data)
170180
}
171181
return depGraphsData, nil
172182
}
173183

184+
func emptyDepGraph(finding *scaplugin.Finding) (*depgraph.DepGraph, error) {
185+
builder, err := depgraph.NewBuilder(
186+
&depgraph.PkgManager{Name: finding.Metadata.Name},
187+
&depgraph.PkgInfo{Name: finding.Metadata.Name, Version: finding.Metadata.Version},
188+
)
189+
if err != nil {
190+
return nil, fmt.Errorf("failed to build depgraph: %w", err)
191+
}
192+
depGraph := builder.Build()
193+
return depGraph, nil
194+
}
195+
174196
func extractDepGraphsFromScans(scans []*snykclient.ScanResult, targetFile string) ([]workflow.Data, error) {
175197
var depGraphList []workflow.Data
176198

@@ -180,20 +202,11 @@ func extractDepGraphsFromScans(scans []*snykclient.ScanResult, targetFile string
180202
if fact.Type != "depGraph" {
181203
continue
182204
}
183-
// Marshal the depgraph data to JSON bytes
184-
depGraphBytes, err := json.Marshal(fact.Data)
185-
if err != nil {
186-
return nil, fmt.Errorf("failed to marshal depgraph data: %w", err)
187-
}
188205

189206
// Create workflow data with the depgraph
190-
data := workflow.NewData(DataTypeID, contentTypeJSON, depGraphBytes)
191-
192-
data.SetMetaData(contentLocationKey, targetFile)
193-
data.SetMetaData(MetaKeyNormalisedTargetFile, targetFile)
194-
195-
if scan.Identity.Type != "" {
196-
data.SetMetaData(MetaKeyTargetFileFromPlugin, scan.Identity.Type)
207+
data, err := workflowDataFromDepGraph(fact.Data, targetFile, scan.Identity.Type)
208+
if err != nil {
209+
return nil, fmt.Errorf("failed to create workflow data: %w", err)
197210
}
198211

199212
depGraphList = append(depGraphList, data)
@@ -202,3 +215,21 @@ func extractDepGraphsFromScans(scans []*snykclient.ScanResult, targetFile string
202215

203216
return depGraphList, nil
204217
}
218+
219+
func workflowDataFromDepGraph(depGraph any, normalisedTargetFile, targetFileFromPlugin string) (workflow.Data, error) {
220+
depGraphBytes, err := json.Marshal(depGraph)
221+
if err != nil {
222+
return nil, fmt.Errorf("failed to marshal depgraph data: %w", err)
223+
}
224+
225+
data := workflow.NewData(DataTypeID, contentTypeJSON, depGraphBytes)
226+
227+
data.SetMetaData(contentLocationKey, normalisedTargetFile)
228+
data.SetMetaData(MetaKeyNormalisedTargetFile, normalisedTargetFile)
229+
230+
if targetFileFromPlugin != "" {
231+
data.SetMetaData(MetaKeyTargetFileFromPlugin, targetFileFromPlugin)
232+
}
233+
234+
return data, nil
235+
}

pkg/sca_plugin/interface.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@ import "github.com/rs/zerolog"
44

55
type Options struct{}
66

7+
type Metadata struct {
8+
PackageManager string
9+
Name string
10+
Version string
11+
}
12+
713
type Finding struct {
814
Sbom Sbom // The raw SBOM bytes
15+
Metadata Metadata // Information about the finding
916
FileExclusions []string // Paths for files that other plugins should ignore
1017
NormalisedTargetFile string // The target file name without any qualifiers, e.g. `uv.lock` (and not `dir/uv.lock`)
1118
}

0 commit comments

Comments
 (0)