Skip to content

Commit 811d2c6

Browse files
committed
add scoring capbility and related functionality
1 parent 4490f72 commit 811d2c6

File tree

6 files changed

+514
-72
lines changed

6 files changed

+514
-72
lines changed

pkg/scorer/v2/engine.go

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import (
2323
"github.com/interlynk-io/sbomqs/pkg/sbom"
2424
)
2525

26-
func ScoreSBOM(ctx context.Context, config Config, paths []string) ([]ScoreResult, error) {
26+
func ScoreSBOM(ctx context.Context, config Config, paths []string) ([]Result, error) {
2727
log := logger.FromContext(ctx)
28-
var results []ScoreResult
29-
var anyProcessed bool
28+
29+
// var results []Result
30+
// var anyProcessed bool
3031

3132
// Validate paths
3233
validPaths := validatePaths(ctx, paths)
@@ -39,69 +40,68 @@ func ScoreSBOM(ctx context.Context, config Config, paths []string) ([]ScoreResul
3940
return nil, fmt.Errorf("failed to validate SBOM configuration: %w", err)
4041
}
4142

42-
// 3) Process each valid input
43-
log.Debugf("processing %d SBOM inputs", len(validPaths))
43+
results := make([]Result, 0, len(validPaths))
44+
var anyProcessed bool
4445

45-
for _, p := range validPaths {
46+
for _, path := range validPaths {
4647
switch {
47-
case IsURL(p):
48-
log.Debugf("processing URL: %s", p)
48+
case IsURL(path):
49+
log.Debugf("processing URL: %s", path)
50+
51+
// sbomFile, sig, err := processURLInput(ctx, p, config)
52+
// if err != nil {
53+
// log.Warnf("failed to process URL %s: %v", p, err)
54+
// continue
55+
// }
56+
// func() {
57+
// defer func() {
58+
// _ = sbomFile.Close()
59+
// _ = os.Remove(sbomFile.Name())
60+
// }()
61+
// res, err := processSBOMInput(ctx, sbomFile, sig, config, p)
62+
// if err != nil {
63+
// log.Warnf("failed to score SBOM from URL %s: %v", p, err)
64+
// return
65+
// }
66+
// results = append(results, res)
67+
// anyProcessed = true
68+
// }()
69+
70+
case IsDir(path):
71+
// dirResults := processDirectory(ctx, p, config)
72+
// if len(dirResults) > 0 {
73+
// results = append(results, dirResults...)
74+
// anyProcessed = true
75+
// }
4976

50-
sbomFile, sig, err := processURLInput(ctx, p, config) // returns *os.File (temp) + signature bundle
77+
default:
78+
log.Debugf("processing file: %s", path)
79+
80+
file, err := getFileHandle(ctx, path)
5181
if err != nil {
52-
log.Warnf("failed to process URL %s: %v", p, err)
82+
log.Warnf("failed to open file %s: %v", path, err)
5383
continue
5484
}
55-
func() { // ensure cleanup per-iteration
56-
defer func() {
57-
_ = sbomFile.Close()
58-
_ = os.Remove(sbomFile.Name())
59-
}()
60-
res, err := processSBOMInput(ctx, sbomFile, sig, config, p)
61-
if err != nil {
62-
log.Warnf("failed to score SBOM from URL %s: %v", p, err)
63-
return
64-
}
65-
results = append(results, res)
66-
anyProcessed = true
67-
}()
68-
69-
case IsDir(p):
70-
log.Debugf("processing directory: %s", p)
71-
dirResults := processDirectory(ctx, p, config) // []ScoreResult (skip bad files internally)
72-
if len(dirResults) > 0 {
73-
results = append(results, dirResults...)
74-
anyProcessed = true
75-
}
76-
77-
default:
78-
if _, err := os.Stat(p); err != nil {
79-
log.Warnf("cannot stat path %s: %v", p, err)
80-
continue
85+
defer file.Close()
86+
87+
signature, err := getSignature(
88+
ctx,
89+
path,
90+
config.SignatureBundle.SigValue,
91+
config.SignatureBundle.PublicKey,
92+
)
93+
if err != nil {
94+
return nil, fmt.Errorf("get signature for %q: %w", path, err)
8195
}
82-
log.Debugf("processing file: %s", p)
8396

84-
f, err := getFileHandle(ctx, p) // *os.File
97+
res, err := SBOMEvaluation(ctx, file, signature, config, path)
8598
if err != nil {
86-
log.Warnf("failed to open file %s: %v", p, err)
87-
continue
99+
log.Warnf("failed to process SBOM %s: %v", path, err)
100+
return nil, fmt.Errorf("process SBOM %q: %w", path, err)
88101
}
89-
func() {
90-
defer f.Close()
91-
92-
sig, err := getSignature(ctx, p, config.SignatureBundle.SigValue, config.SignatureBundle.PublicKey)
93-
if err != nil {
94-
log.Warnf("failed to get signature for %s: %v", p, err)
95-
return
96-
}
97-
res, err := processSBOMInput(ctx, f, sig, config, p)
98-
if err != nil {
99-
log.Warnf("failed to process SBOM %s: %v", p, err)
100-
return
101-
}
102-
results = append(results, res)
103-
anyProcessed = true
104-
}()
102+
103+
results = append(results, res)
104+
anyProcessed = true
105105
}
106106
}
107107

pkg/scorer/v2/registry.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package v2
2+
3+
var Identification = CategorySpec{
4+
Name: "Identification",
5+
Weight: 10,
6+
Features: []FeatureSpec{
7+
{Key: "comp_with_name", Weight: 0.40, Eval: CompWithName},
8+
{Key: "comp_with_version", Weight: 0.35, Eval: CompWithVersion},
9+
{Key: "comp_with_ids", Weight: 0.25, Eval: CompWithUniqIDs},
10+
},
11+
}
12+
13+
var Provenance = CategorySpec{
14+
Name: "Provenance",
15+
Weight: 12,
16+
Features: []FeatureSpec{
17+
{Key: "sbom_creation_timestamp", Weight: 0.20, Eval: SBOMCreationTime},
18+
{Key: "sbom_authors", Weight: 0.20, Eval: SBOMAuthors},
19+
{Key: "sbom_tool_version", Weight: 0.20, Eval: SBOMToolVersion},
20+
{Key: "sbom_supplier", Weight: 0.15, Eval: SBOMSupplier},
21+
{Key: "sbom_namespace", Weight: 0.15, Eval: SBOMNamespace},
22+
{Key: "sbom_lifecycle", Weight: 0.10, Eval: SBOMLifecycle},
23+
},
24+
}
25+
26+
var Integrity = CategorySpec{
27+
Name: "Integrity",
28+
Weight: 15,
29+
Features: []FeatureSpec{
30+
{Key: "sbom_signature", Weight: 0.10, Eval: SBOMDDocSignature},
31+
{Key: "comp_with_hash", Weight: 0.10, Eval: CompWithChecksum},
32+
},
33+
}
34+
35+
var Completeness = CategorySpec{
36+
Name: "Completeness",
37+
Weight: 12,
38+
Features: []FeatureSpec{
39+
{Key: "sbom_signature", Weight: 0.10, Eval: SBOMDDocSignature},
40+
{Key: "comp_with_dependencies", Weight: 0.25, Eval: CompWithDependencies},
41+
{Key: "comp_with_declared_completeness", Weight: 0.15, Eval: CompWithDeclaredCompleteness},
42+
{Key: "primary_component", Weight: 0.15, Eval: PrimaryComponent},
43+
{Key: "comp_with_declared_completeness", Weight: 0.15, Eval: CompWithDeclaredCompleteness},
44+
},
45+
}

pkg/scorer/v2/result.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,16 @@ type ScoreResult struct {
4343
Overall float64
4444
Categories []CategoryResult
4545
}
46+
47+
// Result represents result of an SBOM
48+
type Result struct {
49+
Spec string
50+
SpecVersion string
51+
FileFormat string
52+
Filename string
53+
NumComponents int
54+
CreationTime string
55+
InterlynkScore float64
56+
Grade string
57+
Categories []CategoryResult
58+
}

pkg/scorer/v2/spec.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ import "github.com/interlynk-io/sbomqs/pkg/sbom"
2020
// It returns a raw 0..10 score, a description, and whether to ignore (N/A).
2121
type FeatureFunc func(doc sbom.Document) FeatureScore
2222

23-
// Feature contains Key, Weight and corresponding evaluating funtion.
23+
// FeatureSpec represents properties of a feature.
2424
type FeatureSpec struct {
25-
Key string
26-
Weight float64
27-
Eval FeatureFunc
25+
Key string
26+
Weight float64
27+
Evaluate FeatureFunc
2828
}
2929

30-
// Category contains Name, Weight and collection of features.
30+
// CategorySpec represent properties of a category.
3131
type CategorySpec struct {
3232
Name string
3333
Weight float64
@@ -36,13 +36,23 @@ type CategorySpec struct {
3636

3737
type Config struct {
3838
// Categories to score (e.g., "provenance", "completeness")
39-
Categories []CategorySpec
39+
Categories []string
4040

4141
// Features to score (e.g., "components", "dependencies")
42-
Features []FeatureSpec
42+
Features []string
4343

4444
// Optional path to a config file for filters
4545
ConfigFile string
4646

4747
SignatureBundle sbom.Signature
4848
}
49+
50+
// extractMeta pulls the data to show in the final output.
51+
type interlynkMeta struct {
52+
Filename string
53+
NumComponents int
54+
CreationTime string
55+
Spec string
56+
SpecVersion string
57+
FileFormat string
58+
}

pkg/scorer/v2/utils.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
package v2
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"io"
2021
"net/http"
2122
"net/url"
2223
"os"
2324
"regexp"
2425
"strings"
26+
27+
"github.com/interlynk-io/sbomqs/pkg/logger"
2528
)
2629

2730
func IsDir(path string) bool {
@@ -93,3 +96,35 @@ func DownloadURL(url string) ([]byte, error) {
9396

9497
return data, err
9598
}
99+
100+
func RemoveEmptyStrings(input []string) []string {
101+
var output []string
102+
for _, s := range input {
103+
if trimmed := strings.TrimSpace(s); trimmed != "" {
104+
output = append(output, trimmed)
105+
}
106+
}
107+
return output
108+
}
109+
110+
func normalizeAndValidateCategories(ctx context.Context, categories []string) ([]string, error) {
111+
log := logger.FromContext(ctx)
112+
var normalized []string
113+
114+
for _, c := range categories {
115+
116+
// normalize using alias
117+
if alias, ok := CategoryAliases[c]; ok {
118+
c = alias
119+
}
120+
121+
// validate if it's a supported category
122+
if !SupportedCategories[c] {
123+
log.Warnf("unsupported category: %s", c)
124+
continue
125+
}
126+
normalized = append(normalized, c)
127+
}
128+
129+
return normalized, nil
130+
}

0 commit comments

Comments
 (0)