Skip to content

Commit 15ce10e

Browse files
committed
Add more info to sbom
1 parent dd91b53 commit 15ce10e

3 files changed

Lines changed: 58 additions & 11 deletions

File tree

dfetch/commands/report.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ def __call__(self, args: argparse.Namespace) -> None:
6464
"""Generate the report."""
6565
manifest, path = dfetch.manifest.manifest.get_manifest()
6666

67-
reporter = REPORTERS[args.type]()
68-
6967
with dfetch.util.util.in_directory(os.path.dirname(path)):
68+
reporter = REPORTERS[args.type](path)
69+
7070
for project in manifest.selected_projects(args.projects):
7171
determined_licenses = self._determine_licenses(project)
7272
version = self._determine_version(project)

dfetch/reporting/reporter.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""Abstract reporting interface."""
22

3+
import io
4+
import re
35
from abc import ABC, abstractmethod
4-
from typing import List
6+
from typing import List, Tuple
57

68
from dfetch.manifest.project import ProjectEntry
79
from dfetch.util.license import License
@@ -12,6 +14,16 @@ class Reporter(ABC):
1214

1315
name: str = "abstract"
1416

17+
def __init__(self, manifest_path: str) -> None:
18+
"""Create the reporter.
19+
20+
Args:
21+
manifest_path (str): The path to the manifest.
22+
"""
23+
self._manifest_path = manifest_path
24+
with open(self._manifest_path, "r", encoding="utf-8") as manifest:
25+
self._manifest_buffer = io.StringIO(manifest.read())
26+
1527
@abstractmethod
1628
def add_project(
1729
self,
@@ -21,6 +33,23 @@ def add_project(
2133
) -> None:
2234
"""Add a project to the report."""
2335

36+
def find_name_in_manifest(self, name: str) -> Tuple[int, int, int]:
37+
"""Find the location of a project name in the manifest."""
38+
self._manifest_buffer.seek(0)
39+
for line_nr, line in enumerate(self._manifest_buffer, start=1):
40+
match = re.search(rf"^\s+-\s*name:\s*(?P<name>{name})\s", line)
41+
42+
if match:
43+
return (
44+
line_nr,
45+
int(match.start("name")) + 1,
46+
int(match.end("name")),
47+
)
48+
raise RuntimeError(
49+
"An entry from the manifest was provided,"
50+
" that doesn't exist in the manifest!"
51+
)
52+
2453
@abstractmethod
2554
def dump_to_file(self, outfile: str) -> bool:
2655
"""Do nothing."""

dfetch/reporting/sbom_reporter.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
)
3333
from cyclonedx.model.contact import OrganizationalEntity
3434
from cyclonedx.model.license import DisjunctiveLicense as CycloneDxLicense
35+
from cyclonedx.model.license import LicenseAcknowledgement
3536
from cyclonedx.output import make_outputter
3637
from cyclonedx.schema import OutputFormat, SchemaVersion
3738

@@ -55,7 +56,9 @@ class SbomReporter(Reporter):
5556
name="dfetch",
5657
version=dfetch.__version__,
5758
bom_ref=f"dfetch-{dfetch.__version__}",
58-
licenses=[CycloneDxLicense(name="MIT License", id="MIT")],
59+
licenses=[
60+
CycloneDxLicense(id="MIT", acknowledgement=LicenseAcknowledgement.DECLARED)
61+
],
5962
external_references=[
6063
ExternalReference(
6164
type=ExternalReferenceType.VCS,
@@ -96,8 +99,9 @@ class SbomReporter(Reporter):
9699

97100
name = "SBoM"
98101

99-
def __init__(self) -> None:
102+
def __init__(self, manifest_path: str) -> None:
100103
"""Start the report."""
104+
super().__init__(manifest_path)
101105
self._bom = Bom()
102106
self._bom.metadata.tools.components.add(self.dfetch_tool)
103107
self._bom.metadata.tools.components.add(cdx_lib_component())
@@ -113,13 +117,20 @@ def add_project(
113117
project.remote_url, version=version, subpath=project.source or None
114118
)
115119

120+
name = project.name if purl.type == "generic" else purl.name
121+
122+
line_nr, start, _ = self.find_name_in_manifest(project.name)
123+
116124
component = Component(
117-
name=project.name,
125+
name=name,
118126
version=version,
127+
bom_ref=f"{project.name}-{version}",
119128
type=ComponentType.LIBRARY,
120129
purl=purl,
121130
evidence=ComponentEvidence(
122-
occurrences=[Occurrence(location="dfetch.yaml")],
131+
occurrences=[
132+
Occurrence(location=self._manifest_path, line=line_nr, offset=start)
133+
],
123134
identity=[
124135
Identity(
125136
field=IdentityField.NAME,
@@ -131,7 +142,7 @@ def add_project(
131142
value="Name as used for project in dfetch.yaml",
132143
)
133144
],
134-
concluded_value=project.name,
145+
concluded_value=name,
135146
),
136147
Identity(
137148
field=IdentityField.VERSION,
@@ -152,7 +163,8 @@ def add_project(
152163
Method(
153164
technique=AnalysisTechnique.MANIFEST_ANALYSIS,
154165
confidence=Decimal.from_float(0.4),
155-
value="Determined from the VCS url as used for project in dfetch.yaml",
166+
value=f"Determined from {project.remote_url} as used"
167+
f" for the project {project.name} in dfetch.yaml",
156168
)
157169
],
158170
concluded_value=purl.to_string(),
@@ -189,11 +201,17 @@ def add_project(
189201
)
190202

191203
for lic in licenses:
192-
cdx_license = CycloneDxLicense(name=lic.name, id=lic.spdx_id)
204+
205+
# License wants either an SPDX id or a name, prefer SPDX id when available
206+
cdx_license = (
207+
CycloneDxLicense(id=lic.spdx_id)
208+
if lic.spdx_id
209+
else CycloneDxLicense(name=lic.name)
210+
)
193211

194212
component.licenses.add(cdx_license)
195213
if component.evidence:
196-
component.evidence.licenses.add(cdx_license.bom_ref)
214+
component.evidence.licenses.add(cdx_license)
197215

198216
self._bom.components.add(component)
199217

0 commit comments

Comments
 (0)