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]