diff --git a/deploy/sbom/conftest.py b/deploy/sbom/conftest.py new file mode 100644 index 000000000..37341c627 --- /dev/null +++ b/deploy/sbom/conftest.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) diff --git a/deploy/sbom/resolve_licenses.py b/deploy/sbom/resolve_licenses.py index fbdfc5fa5..6082f823c 100644 --- a/deploy/sbom/resolve_licenses.py +++ b/deploy/sbom/resolve_licenses.py @@ -344,6 +344,10 @@ def needs_fix(comp: dict) -> bool: if not licenses: return True for entry in licenses: + if "expression" in entry: + if entry["expression"].startswith("sha256:"): + return True + continue lic = entry.get("license", {}) lid = lic.get("id", "") lname = lic.get("name", "") diff --git a/deploy/sbom/test_resolve_licenses.py b/deploy/sbom/test_resolve_licenses.py new file mode 100644 index 000000000..9ef1c767d --- /dev/null +++ b/deploy/sbom/test_resolve_licenses.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for deploy/sbom/resolve_licenses.py.""" + +from __future__ import annotations + +from resolve_licenses import needs_fix + + +def test_empty_licenses_needs_fix() -> None: + assert needs_fix({"licenses": []}) + + +def test_no_licenses_key_needs_fix() -> None: + assert needs_fix({}) + + +def test_sha256_in_license_id_needs_fix() -> None: + assert needs_fix({"licenses": [{"license": {"id": "sha256:abc123"}}]}) + + +def test_sha256_in_license_name_needs_fix() -> None: + assert needs_fix({"licenses": [{"license": {"name": "sha256:abc123"}}]}) + + +def test_sha256_expression_needs_fix() -> None: + assert needs_fix({"licenses": [{"expression": "sha256:abc123"}]}) + + +def test_valid_spdx_expression_no_fix() -> None: + assert not needs_fix({"licenses": [{"expression": "MIT OR Apache-2.0"}]}) + + +def test_valid_license_id_no_fix() -> None: + assert not needs_fix({"licenses": [{"license": {"id": "MIT"}}]}) + + +def test_valid_license_name_no_fix() -> None: + assert not needs_fix({"licenses": [{"license": {"name": "MIT"}}]}) diff --git a/tasks/sbom.toml b/tasks/sbom.toml index 452b6aeb5..6b320c49f 100644 --- a/tasks/sbom.toml +++ b/tasks/sbom.toml @@ -69,21 +69,11 @@ UNRESOLVED=0 for f in "$OUTPUT_DIR"/*.cdx.json; do COUNT=$(uv run python -c " import json, sys +sys.path.insert(0, 'deploy/sbom') +from resolve_licenses import needs_fix with open('$f') as fh: sbom = json.load(fh) -missing = 0 -for c in sbom.get('components', []): - lics = c.get('licenses', []) - if not lics: - missing += 1 - continue - for e in lics: - lid = e.get('license', {}).get('id', '') - lname = e.get('license', {}).get('name', '') - if lid.startswith('sha256:') or lname.startswith('sha256:'): - missing += 1 - break -print(missing) +print(sum(1 for c in sbom.get('components', []) if needs_fix(c))) ") if [ "$COUNT" -gt 0 ]; then echo " $(basename "$f"): $COUNT components with unresolved licenses" @@ -99,3 +89,7 @@ else echo "All licenses resolved." fi """ + +["test:sbom"] +description = "Run SBOM unit tests" +run = "uv run pytest deploy/sbom/test_resolve_licenses.py -v"