Skip to content

Commit 1a66dbc

Browse files
committed
initiated with profiles config
1 parent a92afd5 commit 1a66dbc

File tree

4 files changed

+180
-6
lines changed

4 files changed

+180
-6
lines changed

pkg/scorer/v2/profiles/base.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 profiles
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"os"
21+
"strings"
22+
23+
"github.com/stretchr/testify/assert/yaml"
24+
)
25+
26+
// YAML schema + loader (from file or built-ins).
27+
28+
// ReadProfileFile reads, unmarshal, validates and returns a config.
29+
func ReadProfileFile(path string) (*Config, error) {
30+
cfgData, err := os.ReadFile(path)
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
var cfg Config
36+
if err := yaml.Unmarshal(cfgData, &cfg); err != nil {
37+
return nil, fmt.Errorf("profiles: yaml decode: %w", err)
38+
}
39+
40+
if err := validateConfig(&cfg); err != nil {
41+
return nil, err
42+
}
43+
return &cfg, nil
44+
}
45+
46+
func validateConfig(cfg *Config) error {
47+
if cfg == nil {
48+
return errors.New("profiles: nil")
49+
}
50+
51+
if len(cfg.Profiles) == 0 {
52+
return errors.New("profiles: no profiles found")
53+
}
54+
55+
profileExist := make(map[string]bool)
56+
57+
for i, profile := range cfg.Profiles {
58+
59+
// validate profile name, duplicacy, etc
60+
if err := validateProfile(i, profile, profileExist); err != nil {
61+
return err
62+
}
63+
64+
// validate profile features
65+
if err := validateProfileFeatures(profile); err != nil {
66+
return err
67+
}
68+
}
69+
70+
return nil
71+
}
72+
73+
func validateProfileFeatures(profile Profile) error {
74+
// validate profile features
75+
featureExists := make(map[string]bool)
76+
for i, feat := range profile.Features {
77+
key := strings.TrimSpace(feat.Key)
78+
79+
if key == "" {
80+
return fmt.Errorf("profiles: profile %q has empty feature.name at index %d", profile.Name, i)
81+
}
82+
83+
if _, dup := featureExists[key]; dup {
84+
return fmt.Errorf("profiles: profile %q has duplicate feature %q", profile.Name, key)
85+
}
86+
87+
featureExists[key] = true
88+
89+
// now validate this key with that profile having list of keys already with it.
90+
}
91+
return nil
92+
}
93+
94+
func validateProfile(i int, profile Profile, profileExist map[string]bool) error {
95+
pfName := strings.TrimSpace(profile.Name)
96+
if pfName == "" {
97+
return fmt.Errorf("profiles: profile at index %d has empty name", i)
98+
}
99+
100+
if _, exists := profileExist[pfName]; exists {
101+
return fmt.Errorf("profiles: duplicate profile name %q", pfName)
102+
}
103+
104+
profileExist[pfName] = true
105+
106+
if len(profile.Features) == 0 {
107+
return fmt.Errorf("profiles: profile %q has no features", pfName)
108+
}
109+
110+
return nil
111+
}

pkg/scorer/v2/profiles/config.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 profiles
16+
17+
// YAML schema + loader (from file or built-ins).
18+
19+
// File is the root of profiles.yaml
20+
type Config struct {
21+
SBOMQS SBOMQSMeta `yaml:"sbomqs"`
22+
Profiles []Profile `yaml:"profiles"`
23+
}
24+
25+
// SBOMQSMeta is a small header so we can warn on incompatible files.
26+
type SBOMQSMeta struct {
27+
Version string `yaml:"version"` // e.g., "2.0.0"
28+
Description string `yaml:"description"` // free text
29+
LastUpdated string `yaml:"last_updated"` // 2025-10-15
30+
}

pkg/scorer/v2/profiles/profile.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 profiles
16+
17+
import "github.com/interlynk-io/sbomqs/pkg/scorer/v2/config"
18+
19+
// Profile is one compliance profile (e.g., ntia, bsi-v2.0, oct).
20+
type Profile struct {
21+
Name string `yaml:"name"` // short key: "ntia"
22+
FullName string `yaml:"full_name"` // display: "NTIA Minimum Elements"
23+
Description string `yaml:"description"`
24+
Features []config.FeatureSpec `yaml:"features"`
25+
}

pkg/scorer/v2/score/registry.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,23 @@ var LicensingAndCompliance = config.CategorySpec{
146146
}
147147

148148
var VulnerabilityAndTraceability = config.CategorySpec{
149-
Name: "Vulnerability & Traceability",
150-
Weight: 10,
151-
Features: nil,
149+
Name: "Vulnerability & Traceability",
150+
Weight: 10,
151+
Features: []config.FeatureSpec{
152+
{Key: "CompWithPURL", Weight: 0.50, Evaluate: extractors.CompWithPURL},
153+
{Key: "CompWithCPE", Weight: 0.50, Evaluate: extractors.CompWithCPE},
154+
},
152155
}
153156

154157
var Structural = config.CategorySpec{
155-
Name: "Structural",
156-
Weight: 8,
157-
Features: nil,
158+
Name: "Structural",
159+
Weight: 8,
160+
Features: []config.FeatureSpec{
161+
{Key: "SBOMWithSpec", Weight: 0.30, Evaluate: extractors.SBOMWithSpec},
162+
{Key: "SBOMSpecVersion", Weight: 0.30, Evaluate: extractors.SBOMSpecVersion},
163+
{Key: "SBOMFileFormat", Weight: 0.20, Evaluate: extractors.SBOMFileFormat},
164+
{Key: "SBOMSchemaValid", Weight: 0.20, Evaluate: extractors.SBOMSchemaValid},
165+
},
158166
}
159167

160168
var ComponentAndQualityInfo = config.CategorySpec{

0 commit comments

Comments
 (0)