Skip to content

Commit 7280e7c

Browse files
committed
add reporter for v2
1 parent 8bdba6a commit 7280e7c

File tree

22 files changed

+675
-1247
lines changed

22 files changed

+675
-1247
lines changed

pkg/engine/score.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ import (
2929
"github.com/interlynk-io/sbomqs/pkg/compliance/common"
3030
"github.com/interlynk-io/sbomqs/pkg/logger"
3131
"github.com/interlynk-io/sbomqs/pkg/reporter"
32+
v2 "github.com/interlynk-io/sbomqs/pkg/reporter/v2"
3233
"github.com/interlynk-io/sbomqs/pkg/sbom"
3334
"github.com/interlynk-io/sbomqs/pkg/scorer"
35+
"github.com/interlynk-io/sbomqs/pkg/scorer/v2/config"
36+
score "github.com/interlynk-io/sbomqs/pkg/scorer/v2/score"
37+
3438
"github.com/spf13/afero"
3539
)
3640

@@ -66,6 +70,9 @@ type Params struct {
6670
Signature string
6771
PublicKey string
6872
Blob string
73+
74+
Legacy bool
75+
Profiles []string
6976
}
7077

7178
func Run(ctx context.Context, ep *Params) error {
@@ -77,7 +84,41 @@ func Run(ctx context.Context, ep *Params) error {
7784
log.Fatal("path is required")
7885
}
7986

80-
return handlePaths(ctx, ep)
87+
if ep.Legacy {
88+
return handlePaths(ctx, ep)
89+
}
90+
91+
return scored(ctx, ep)
92+
}
93+
94+
func scored(ctx context.Context, ep *Params) error {
95+
cfg := config.Config{
96+
Categories: ep.Categories,
97+
Features: ep.Features,
98+
ConfigFile: ep.ConfigPath,
99+
Profile: ep.Profiles,
100+
}
101+
102+
results, err := score.ScoreSBOM(ctx, cfg, ep.Path)
103+
if err != nil {
104+
return err
105+
}
106+
107+
reportFormat := "detailed"
108+
if ep.Basic {
109+
reportFormat = "basic"
110+
}
111+
112+
if ep.JSON {
113+
reportFormat = "json"
114+
}
115+
116+
nr := v2.NewReport(ctx, results, reportFormat)
117+
nr.Report()
118+
119+
return nil
120+
121+
// print the score
81122
}
82123

83124
func handleURL(path string) (string, string, error) {

pkg/reporter/v2/basic.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package v2
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/interlynk-io/sbomqs/pkg/sbom"
8+
)
9+
10+
func (r *Reporter) basicReport() {
11+
for _, r := range r.Results {
12+
format := r.Meta.FileFormat
13+
spec := r.Meta.Spec
14+
version := r.Meta.SpecVersion
15+
16+
if spec == string(sbom.SBOMSpecSPDX) {
17+
version = strings.Replace(version, "SPDX-", "", 1)
18+
}
19+
20+
// if spec == string(sbom.SBOMSpecCDX) {
21+
// spec = "cyclonedx"
22+
// }
23+
24+
fmt.Printf("%0.1f\t%s\t%s\t%s\t%s\n", r.InterlynkScore, r.Grade, version, format, r.Meta.Filename)
25+
26+
}
27+
}

pkg/reporter/v2/detailed.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package v2
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/olekukonko/tablewriter"
8+
)
9+
10+
func (r *Reporter) detailedReport() {
11+
fmt.Println("DETAILED SCORE")
12+
for _, r := range r.Results {
13+
outDoc := [][]string{}
14+
for _, cat := range r.Comprehensive.Categories {
15+
for _, feat := range cat.Features {
16+
l := []string{cat.Name, feat.Key, fmt.Sprintf("%.1f/10.0", feat.Score), feat.Desc}
17+
outDoc = append(outDoc, l)
18+
}
19+
}
20+
21+
fmt.Printf("\n SBOM Quality Score: %0.1f/10.0\t Grade: %s\tComponents: %d\t%s\t\n\n", r.InterlynkScore, r.Grade, r.Meta.NumComponents, r.Meta.Filename)
22+
23+
// Initialize tablewriter table with borders
24+
table := tablewriter.NewWriter(os.Stdout)
25+
table.SetHeader([]string{"Category", "Feature", "Score", "Desc"})
26+
table.SetRowLine(true)
27+
table.SetAutoMergeCellsByColumnIndex([]int{0})
28+
table.AppendBulk(outDoc)
29+
table.Render()
30+
}
31+
}

pkg/reporter/v2/json.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package v2
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
"github.com/google/uuid"
10+
"github.com/interlynk-io/sbomqs/pkg/sbom"
11+
"sigs.k8s.io/release-utils/version"
12+
)
13+
14+
type score struct {
15+
Category string `json:"category"`
16+
Feature string `json:"feature"`
17+
Score float64 `json:"score"`
18+
Desc string `json:"description"`
19+
Ignored bool `json:"ignored"`
20+
}
21+
22+
type file struct {
23+
Name string `json:"file_name"`
24+
Spec string `json:"spec"`
25+
SpecVersion string `json:"spec_version"`
26+
Format string `json:"file_format"`
27+
Grade string `json:"grade"`
28+
InterlynkScore float64 `json:"interlynk_score"`
29+
Components int `json:"num_components"`
30+
CreationTime string `json:"creation_time"`
31+
Scores []*score `json:"scores"`
32+
}
33+
34+
type creation struct {
35+
Name string `json:"name"`
36+
Version string `json:"version"`
37+
ScoringEngine string `json:"scoring_engine_version"`
38+
Vendor string `json:"vendor"`
39+
}
40+
41+
type jsonReport struct {
42+
RunID string `json:"run_id"`
43+
TimeStamp string `json:"timestamp"`
44+
CreationInfo creation `json:"creation_info"`
45+
Files []file `json:"files"`
46+
}
47+
48+
func newJSONReport() *jsonReport {
49+
return &jsonReport{
50+
RunID: uuid.New().String(),
51+
TimeStamp: time.Now().UTC().Format(time.RFC3339),
52+
CreationInfo: creation{
53+
Name: "sbomqs",
54+
Version: version.GetVersionInfo().GitVersion,
55+
// ScoringEngine: scorer.EngineVersion,
56+
Vendor: "Interlynk (support@interlynk.io)",
57+
},
58+
Files: []file{},
59+
}
60+
}
61+
62+
func (r *Reporter) jsonReport() (string, error) {
63+
fmt.Println("JSON SCORE")
64+
jr := newJSONReport()
65+
66+
for _, r := range r.Results {
67+
f := file{}
68+
f.InterlynkScore = r.InterlynkScore
69+
f.Grade = r.Grade
70+
f.Components = r.Meta.NumComponents
71+
f.Format = r.Meta.FileFormat
72+
f.Name = r.Meta.Filename
73+
f.Spec = r.Meta.Spec
74+
f.CreationTime = r.Meta.CreationTime
75+
76+
if r.Meta.Spec == string(sbom.SBOMSpecSPDX) {
77+
version := strings.Replace(r.Meta.SpecVersion, "SPDX-", "", 1)
78+
f.SpecVersion = version
79+
}
80+
81+
for _, cat := range r.Comprehensive.Categories {
82+
for _, feat := range cat.Features {
83+
ns := new(score)
84+
ns.Category = cat.Name
85+
ns.Feature = feat.Key
86+
ns.Score = feat.Score
87+
ns.Desc = feat.Desc
88+
ns.Ignored = feat.Ignored
89+
f.Scores = append(f.Scores, ns)
90+
}
91+
}
92+
93+
jr.Files = append(jr.Files, f)
94+
}
95+
96+
o, err := json.MarshalIndent(jr, "", " ")
97+
if err != nil {
98+
return "", err
99+
}
100+
101+
if true {
102+
fmt.Println(string(o))
103+
}
104+
105+
return string(o), nil
106+
}

pkg/reporter/v2/profile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package v2

pkg/reporter/v2/reporter.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
6+
"github.com/interlynk-io/sbomqs/pkg/scorer/v2/api"
7+
)
8+
9+
type ReportFormat string
10+
11+
var (
12+
REPORT_BASIC ReportFormat = "basic"
13+
REPORT_JSON ReportFormat = "json"
14+
REPORT_DETAILED ReportFormat = "detailed"
15+
)
16+
17+
type Reporter struct {
18+
Ctx context.Context
19+
Results []api.Result
20+
Format ReportFormat
21+
}
22+
23+
func NewReport(ctx context.Context, results []api.Result, rFormat string) *Reporter {
24+
report := &Reporter{
25+
Ctx: ctx,
26+
Results: results,
27+
Format: ReportFormat(rFormat),
28+
}
29+
30+
return report
31+
}
32+
33+
func (r *Reporter) Report() {
34+
switch r.Format {
35+
case REPORT_BASIC:
36+
// basic report
37+
r.basicReport()
38+
39+
case REPORT_JSON:
40+
// json report
41+
r.jsonReport()
42+
43+
case REPORT_DETAILED:
44+
r.detailedReport()
45+
46+
}
47+
}

pkg/scorer/v2/api/result.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ import (
2121

2222
// Result represents result of an SBOM
2323
type Result struct {
24-
Meta SBOMMeta
24+
Doc sbom.Document
25+
Meta SBOMMeta
26+
InterlynkScore float64
27+
Grade string
2528

2629
Comprehensive *ComprehensiveResult
2730
Profiles []ProfileResult
@@ -39,9 +42,7 @@ type SBOMMeta struct {
3942

4043
// Comprehensive (quality) scoring
4144
type ComprehensiveResult struct {
42-
InterlynkScore float64
43-
Grade string
44-
Categories []CategoryResult
45+
Categories []CategoryResult
4546
}
4647

4748
// Category result
@@ -54,6 +55,7 @@ type CategoryResult struct {
5455

5556
// feature result
5657
type FeatureResult struct {
58+
Name string
5759
Key string
5860
Weight float64 // feature weight
5961
Score float64
@@ -101,6 +103,19 @@ func NewProfFeatResult(pFeat catalog.ProfFeatSpec) ProfileFeatureResult {
101103
}
102104
}
103105

106+
func NewComprFeatResult(comprFeat catalog.ComprFeatSpec) FeatureResult {
107+
return FeatureResult{
108+
Name: comprFeat.Name,
109+
Key: string(comprFeat.Key),
110+
Score: 0.0,
111+
Weight: comprFeat.Weight,
112+
}
113+
}
114+
115+
func NewComprResult() ComprehensiveResult {
116+
return ComprehensiveResult{}
117+
}
118+
104119
func NewCategoryResultFromSpec(cat catalog.ComprCatSpec) CategoryResult {
105120
return CategoryResult{
106121
Name: cat.Name,

pkg/scorer/v2/catalog/catalog.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,8 @@ func (c *Catalog) ResolveProfileAlias(s string) (ProfileKey, bool) {
8080
return k, ok
8181
}
8282

83-
func (c *Catalog) BaseCategories() []ComprCatSpec {
84-
out := make([]ComprCatSpec, 0, len(c.Order))
85-
for _, k := range c.Order {
86-
if spec, ok := c.ComprCategories[k]; ok {
87-
out = append(out, spec)
88-
}
89-
}
90-
return out
83+
func (c *Catalog) BaseCategoriesKeys() []ComprCatKey {
84+
return c.Order
9185
}
9286

9387
func (c *Catalog) BaseProfiles() []ProfSpec {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2025 Interlynk.io
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package catalog

0 commit comments

Comments
 (0)