88
99 "github.com/rezmoss/sbomlyze/internal/analysis"
1010 "github.com/rezmoss/sbomlyze/internal/cli"
11+ "github.com/rezmoss/sbomlyze/internal/compliance"
1112 "github.com/rezmoss/sbomlyze/internal/convert"
1213 "github.com/rezmoss/sbomlyze/internal/output"
1314 "github.com/rezmoss/sbomlyze/internal/pager"
@@ -126,18 +127,50 @@ func main() {
126127 p := pager .Start (opts .NoPager )
127128 defer p .Stop ()
128129
130+ // Load policy when given so path/parse errors surface
131+ var pol policy.Policy
132+ havePolicy := opts .PolicyFile != ""
133+ if havePolicy {
134+ policyData , err := os .ReadFile (opts .PolicyFile )
135+ if err != nil {
136+ fmt .Fprintf (os .Stderr , "err: read policy: %v\n " , err )
137+ os .Exit (1 )
138+ }
139+ pol , err = policy .Load (policyData )
140+ if err != nil {
141+ fmt .Fprintf (os .Stderr , "err: parse policy: %v\n " , err )
142+ os .Exit (1 )
143+ }
144+ }
145+
146+ // Evaluate on --compliance, or when policy has score thresholds (so
147+ // they can't be skipped by omitting the flag). Shown only on --compliance.
148+ var complianceReport * compliance.Report
149+ var complianceViolations []policy.Violation
150+ if opts .Compliance || (havePolicy && policy .HasComplianceRules (pol )) {
151+ r := compliance .Evaluate (comps , sbomInfo )
152+ if opts .Compliance {
153+ complianceReport = & r
154+ }
155+ if havePolicy {
156+ complianceViolations = policy .EvaluateCompliance (pol , r )
157+ }
158+ }
159+
129160 switch opts .Format {
130161 case "json" :
131162 out := struct {
132- Info sbom.SBOMInfo `json:"info"`
133- Findings analysis.KeyFindings `json:"findings"`
134- Stats analysis.Stats `json:"stats"`
135- Warnings []cli.ParseWarning `json:"warnings,omitempty"`
163+ Info sbom.SBOMInfo `json:"info"`
164+ Findings analysis.KeyFindings `json:"findings"`
165+ Stats analysis.Stats `json:"stats"`
166+ Compliance * compliance.Report `json:"compliance,omitempty"`
167+ Warnings []cli.ParseWarning `json:"warnings,omitempty"`
136168 }{
137- Info : sbomInfo ,
138- Findings : findings ,
139- Stats : stats ,
140- Warnings : parseOpts .Warnings ,
169+ Info : sbomInfo ,
170+ Findings : findings ,
171+ Stats : stats ,
172+ Compliance : complianceReport ,
173+ Warnings : parseOpts .Warnings ,
141174 }
142175 enc := json .NewEncoder (os .Stdout )
143176 enc .SetIndent ("" , " " )
@@ -147,13 +180,30 @@ func main() {
147180 os .Exit (1 )
148181 }
149182 case "html" :
150- fmt .Println (output .GenerateHTMLStats (stats , sbomInfo , findings ))
183+ fmt .Println (output .GenerateHTMLStatsWithCompliance (stats , sbomInfo , findings , complianceReport ))
151184 default :
152185 output .PrintSingleScanContext (sbomInfo )
153186 output .PrintKeyFindings (findings )
154187 analysis .PrintStats (stats )
155188 cli .PrintWarnings (parseOpts .Warnings )
189+ if complianceReport != nil {
190+ compliance .PrintReport (* complianceReport )
191+ }
192+ }
193+
194+ if len (complianceViolations ) > 0 {
195+ // Policy violations go to stderr (single-file mode). For non-text
196+ // formats, printing to stdout would corrupt the JSON/HTML output.
197+ w := os .Stdout
198+ if opts .Format == "json" || opts .Format == "html" {
199+ w = os .Stderr
200+ }
201+ output .PrintViolationsTo (w , complianceViolations )
202+ if policy .HasErrors (complianceViolations ) {
203+ os .Exit (1 )
204+ }
156205 }
206+
157207 return
158208 }
159209
@@ -189,13 +239,14 @@ func main() {
189239 spin .Done ("Done" )
190240
191241 var violations []policy.Violation
242+ var pol policy.Policy
192243 if opts .PolicyFile != "" {
193244 policyData , err := os .ReadFile (opts .PolicyFile )
194245 if err != nil {
195246 fmt .Fprintf (os .Stderr , "err: read policy: %v\n " , err )
196247 os .Exit (1 )
197248 }
198- pol , err : = policy .Load (policyData )
249+ pol , err = policy .Load (policyData )
199250 if err != nil {
200251 fmt .Fprintf (os .Stderr , "err: parse policy: %v\n " , err )
201252 os .Exit (1 )
@@ -208,6 +259,19 @@ func main() {
208259 sbomFile = opts .Files [1 ]
209260 }
210261
262+ // Evaluate on "after" SBOM (2nd file) on --compliance, or when policy has
263+ // score thresholds (can't skip via no flag). Shown only on --compliance.
264+ var complianceReport * compliance.Report
265+ if opts .Compliance || (opts .PolicyFile != "" && policy .HasComplianceRules (pol )) {
266+ r := compliance .Evaluate (comps2 , info2 )
267+ if opts .Compliance {
268+ complianceReport = & r
269+ }
270+ if opts .PolicyFile != "" {
271+ violations = append (violations , policy .EvaluateCompliance (pol , r )... )
272+ }
273+ }
274+
211275 p := pager .Start (opts .NoPager )
212276
213277 switch opts .Format {
@@ -217,12 +281,14 @@ func main() {
217281 Findings analysis.KeyFindings `json:"findings"`
218282 Diff analysis.DiffResult `json:"diff"`
219283 Violations []policy.Violation `json:"violations,omitempty"`
284+ Compliance * compliance.Report `json:"compliance,omitempty"`
220285 Warnings []cli.ParseWarning `json:"warnings,omitempty"`
221286 }{
222287 Overview : overview ,
223288 Findings : findings ,
224289 Diff : result ,
225290 Violations : violations ,
291+ Compliance : complianceReport ,
226292 Warnings : parseOpts .Warnings ,
227293 }
228294 enc := json .NewEncoder (os .Stdout )
@@ -254,10 +320,10 @@ func main() {
254320 fmt .Println (xml .Header + string (out ))
255321
256322 case "markdown" , "md" :
257- fmt .Println (output .GenerateMarkdownWithOverview (result , violations , overview , findings ))
323+ fmt .Println (output .GenerateMarkdownWithOverviewAndCompliance (result , violations , overview , findings , complianceReport ))
258324
259325 case "html" :
260- fmt .Println (output .GenerateHTML (result , violations , overview , findings ))
326+ fmt .Println (output .GenerateHTMLWithCompliance (result , violations , overview , findings , complianceReport ))
261327
262328 case "patch" :
263329 patch := output .GenerateJSONPatch (result )
@@ -277,6 +343,9 @@ func main() {
277343 output .PrintTextDiff (result )
278344 output .PrintViolations (violations )
279345 cli .PrintWarnings (parseOpts .Warnings )
346+ if complianceReport != nil {
347+ compliance .PrintReport (* complianceReport )
348+ }
280349 }
281350
282351 p .Stop ()
0 commit comments