This is an automated email from the ASF dual-hosted git repository.

arm pushed a commit to branch previous_sbom_results
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/previous_sbom_results by this 
push:
     new 6d267b9  Show previous/changed licenses
6d267b9 is described below

commit 6d267b93f830a9f1dcf593ce2f20b257e2f8eaf8
Author: Alastair McFarlane <[email protected]>
AuthorDate: Tue Dec 23 17:25:17 2025 +0000

    Show previous/changed licenses
---
 atr/get/sbom.py       | 53 ++++++++++++++++++++++++++++-----------------------
 atr/models/results.py |  7 ++-----
 atr/sbom/cli.py       |  2 +-
 atr/sbom/licenses.py  | 18 +++++++++++++++--
 atr/tasks/sbom.py     | 13 +++++++------
 5 files changed, 55 insertions(+), 38 deletions(-)

diff --git a/atr/get/sbom.py b/atr/get/sbom.py
index 552c916..7e61e1b 100644
--- a/atr/get/sbom.py
+++ b/atr/get/sbom.py
@@ -196,34 +196,27 @@ def _license_section(block: htm.Block, task_result: 
results.SBOMToolScore) -> No
     block.h2["Licenses"]
     warnings = []
     errors = []
-    prev_warnings = []
-    prev_errors = []
+    prev_licenses = []
+    if task_result.prev_licenses is not None:
+        prev_licenses = _load_license_issues(task_result.prev_licenses)
     if task_result.license_warnings is not None:
-        prev_warnings, warnings = 
_load_license_issues(task_result.license_warnings, 
task_result.prev_license_warnings)
+        warnings = _load_license_issues(task_result.license_warnings)
     if task_result.license_errors is not None:
-        errors, prev_errors = _load_license_issues(task_result.license_errors, 
task_result.prev_license_errors)
+        errors = _load_license_issues(task_result.license_errors)
     if warnings:
         block.h3[htm.icon("exclamation-triangle-fill", ".me-2.text-warning"), 
"Warnings"]
-        _license_table(block, warnings, prev_warnings)
+        _license_table(block, warnings, prev_licenses)
 
     if errors:
         block.h3[htm.icon("x-octagon-fill", ".me-2.text-danger"), "Errors"]
-        _license_table(block, errors, prev_errors)
+        _license_table(block, errors, prev_licenses)
 
     if not (warnings or errors):
         block.p["No license warnings or errors found."]
 
 
-def _load_license_issues(
-    issues: list[str], prev_issues: list[str] | None
-) -> tuple[list[sbom.models.licenses.Issue], list[sbom.models.licenses.Issue]]:
-    iss = [sbom.models.licenses.Issue.model_validate(json.loads(i)) for i in 
issues]
-    prev = (
-        [sbom.models.licenses.Issue.model_validate(json.loads(i)) for i in 
prev_issues]
-        if prev_issues is not None
-        else []
-    )
-    return iss, prev
+def _load_license_issues(issues: list[str]) -> 
list[sbom.models.licenses.Issue]:
+    return [sbom.models.licenses.Issue.model_validate(json.loads(i)) for i in 
issues]
 
 
 def _report_header(
@@ -330,7 +323,7 @@ def _extract_vulnerability_severity(vuln: 
osv.VulnerabilityDetails) -> str:
 def _license_table(
     block: htm.Block,
     items: list[sbom.models.licenses.Issue],
-    prev: list[sbom.models.licenses.Issue],  # TODO: Compare items to old items
+    prev: list[sbom.models.licenses.Issue],
 ) -> None:
     warning_rows = [
         htm.tr[
@@ -339,9 +332,9 @@ def _license_table(
                 if (len(components) == 0)
                 else htm.details[htm.summary[f"Category {category!s}"], 
htm.div[_detail_table(components)]]
             ],
-            htm.td[str(count)],
+            htm.td[f"{count!s} ({new} new)"],
         ]
-        for category, count, components in _license_tally(items)
+        for category, count, new, components in _license_tally(items, prev)
     ]
     block.table(".table.table-sm.table-bordered.table-striped")[
         htm.thead[htm.tr[htm.th["License Category"], htm.th["Count"]]],
@@ -370,7 +363,7 @@ def _missing_table(block: htm.Block, items: 
list[sbom.models.conformance.Missing
 
 def _detail_table(components: list[str | None]):
     return htm.table(".table.table-sm.table-bordered.table-striped")[
-        htm.tbody[[htm.tr[htm.td[comp.capitalize()]] for comp in components if 
comp is not None]],
+        htm.tbody[[htm.tr[htm.td[comp]] for comp in components if comp is not 
None]],
     ]
 
 
@@ -392,18 +385,30 @@ def _missing_tally(items: 
list[sbom.models.conformance.Missing]) -> list[tuple[s
 
 def _license_tally(
     items: list[sbom.models.licenses.Issue],
-) -> list[tuple[sbom.models.licenses.Category, int, list[str | None]]]:
+    old_issues: list[sbom.models.licenses.Issue],
+) -> list[tuple[sbom.models.licenses.Category, int, int, list[str | None]]]:
     counts: dict[sbom.models.licenses.Category, int] = {}
     components: dict[sbom.models.licenses.Category, list[str | None]] = {}
+    new_count = 0
+    old_map = {lic.component_name: (lic.license_expression, lic.category) for 
lic in old_issues}
     for item in items:
         key = item.category
         counts[key] = counts.get(key, 0) + 1
+        name = str(item).capitalize()
+        if item.component_name not in old_map:
+            new_count = new_count + 1
+            name = f"{name} (new)"
+        elif item.license_expression != old_map[item.component_name][0]:
+            new_count = new_count + 1
+            name = f"{name} (previously {old_map[item.component_name][0]} - 
Category {
+                str(old_map[item.component_name][1]).upper()
+            })"
         if key not in components:
-            components[key] = [str(item)]
+            components[key] = [name]
         else:
-            components[key].append(str(item))
+            components[key].append(name)
     return sorted(
-        [(category, count, components.get(category, [])) for category, count 
in counts.items()],
+        [(category, count, new_count, components.get(category, [])) for 
category, count in counts.items()],
         key=lambda kv: kv[0].value,
     )
 
diff --git a/atr/models/results.py b/atr/models/results.py
index 5b3ee02..5d3ffbc 100644
--- a/atr/models/results.py
+++ b/atr/models/results.py
@@ -144,11 +144,8 @@ class SBOMToolScore(schema.Strict):
     vulnerabilities: list[str] | None = schema.Field(
         default=None, strict=False, description="Vulnerabilities found in the 
SBOM"
     )
-    prev_license_warnings: list[str] | None = schema.Field(
-        default=None, strict=False, description="License warnings from 
previous release"
-    )
-    prev_license_errors: list[str] | None = schema.Field(
-        default=None, strict=False, description="License errors from previous 
release"
+    prev_licenses: list[str] | None = schema.Field(
+        default=None, strict=False, description="Licenses from previous 
release"
     )
     prev_vulnerabilities: list[str] | None = schema.Field(
         default=None, strict=False, description="Vulnerabilities from previous 
release"
diff --git a/atr/sbom/cli.py b/atr/sbom/cli.py
index 512a93c..a9cbd80 100644
--- a/atr/sbom/cli.py
+++ b/atr/sbom/cli.py
@@ -31,7 +31,7 @@ from .utilities import bundle_to_ntia_patch, 
bundle_to_vuln_patch, patch_to_data
 
 
 def command_license(bundle: models.bundle.Bundle) -> None:
-    warnings, errors = check(bundle.bom)
+    _, warnings, errors = check(bundle.bom)
     if warnings:
         print("WARNINGS (Category B):")
         for warning in warnings:
diff --git a/atr/sbom/licenses.py b/atr/sbom/licenses.py
index bfed06a..470571c 100644
--- a/atr/sbom/licenses.py
+++ b/atr/sbom/licenses.py
@@ -23,9 +23,11 @@ from .spdx import license_expression_atoms
 
 def check(
     bom_value: models.bom.Bom,
-) -> tuple[list[models.licenses.Issue], list[models.licenses.Issue]]:
+    include_all: bool = False,
+) -> tuple[list[models.licenses.Issue], list[models.licenses.Issue], 
list[models.licenses.Issue]]:
     warnings: list[models.licenses.Issue] = []
     errors: list[models.licenses.Issue] = []
+    good: list[models.licenses.Issue] = []
 
     components = bom_value.components or []
     if bom_value.metadata and bom_value.metadata.component:
@@ -99,5 +101,17 @@ def check(
                         component_type=type,
                     )
                 )
+            elif include_all:
+                good.append(
+                    models.licenses.Issue(
+                        component_name=name,
+                        component_version=version,
+                        license_expression=license_expr,
+                        category=models.licenses.Category.A,
+                        any_unknown=False,
+                        scope=scope,
+                        component_type=type,
+                    )
+                )
 
-    return warnings, errors
+    return good, warnings, errors
diff --git a/atr/tasks/sbom.py b/atr/tasks/sbom.py
index b11d316..0b2ea04 100644
--- a/atr/tasks/sbom.py
+++ b/atr/tasks/sbom.py
@@ -230,13 +230,12 @@ async def score_tool(args: ScoreArgs) -> results.Results 
| None:
     # TODO: Could update the ATR version with a constant showing last change 
to the augment/scan
     #  tools so we know if it's outdated
     outdated = sbom.tool.plugin_outdated_version(bundle.bom)
-    license_warnings, license_errors = sbom.licenses.check(bundle.bom)
+    _, license_warnings, license_errors = sbom.licenses.check(bundle.bom)
     vulnerabilities = sbom.osv.vulns_from_bundle(bundle)
     cli_errors = sbom.cyclonedx.validate_cli(bundle)
 
     prev_version = None
-    prev_license_warnings = None
-    prev_license_errors = None
+    prev_licenses = None
     prev_vulnerabilities = None
     if previous_base_dir is not None:
         previous_full_path = os.path.join(previous_base_dir, args.file_path)
@@ -247,7 +246,10 @@ async def score_tool(args: ScoreArgs) -> results.Results | 
None:
             previous_bundle = None
         if previous_bundle is not None:
             prev_version, _ = 
sbom.utilities.get_props_from_bundle(previous_bundle)
-            prev_license_warnings, prev_license_errors = 
sbom.licenses.check(previous_bundle.bom)
+            prev_good, prev_license_warnings, prev_license_errors = 
sbom.licenses.check(
+                previous_bundle.bom, include_all=True
+            )
+            prev_licenses = [*prev_good, *prev_license_warnings, 
*prev_license_errors]
             prev_vulnerabilities = sbom.osv.vulns_from_bundle(previous_bundle)
 
     return results.SBOMToolScore(
@@ -264,8 +266,7 @@ async def score_tool(args: ScoreArgs) -> results.Results | 
None:
         license_warnings=[w.model_dump_json() for w in license_warnings] if 
license_warnings else None,
         license_errors=[e.model_dump_json() for e in license_errors] if 
license_errors else None,
         vulnerabilities=[v.model_dump_json() for v in vulnerabilities],
-        prev_license_warnings=[w.model_dump_json() for w in 
prev_license_warnings] if prev_license_warnings else None,
-        prev_license_errors=[e.model_dump_json() for e in prev_license_errors] 
if prev_license_errors else None,
+        prev_licenses=[w.model_dump_json() for w in prev_licenses] if 
prev_licenses else None,
         prev_vulnerabilities=[v.model_dump_json() for v in 
prev_vulnerabilities] if prev_vulnerabilities else None,
         atr_props=properties,
         cli_errors=cli_errors,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to