scanoss 1.12.2__py3-none-any.whl → 1.43.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protoc_gen_swagger/__init__.py +13 -13
- protoc_gen_swagger/options/__init__.py +13 -13
- protoc_gen_swagger/options/annotations_pb2.py +18 -12
- protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
- protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
- protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
- protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
- scanoss/__init__.py +18 -18
- scanoss/api/__init__.py +17 -17
- scanoss/api/common/__init__.py +17 -17
- scanoss/api/common/v2/__init__.py +17 -17
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -20
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- scanoss/api/components/__init__.py +17 -17
- scanoss/api/components/v2/__init__.py +17 -17
- scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -21
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +766 -13
- scanoss/api/dependencies/__init__.py +17 -17
- scanoss/api/dependencies/v2/__init__.py +17 -17
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -29
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +94 -8
- scanoss/api/geoprovenance/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +92 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +381 -0
- scanoss/api/licenses/__init__.py +23 -0
- scanoss/api/licenses/v2/__init__.py +23 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
- scanoss/api/scanning/__init__.py +17 -17
- scanoss/api/scanning/v2/__init__.py +17 -17
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +42 -13
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +86 -7
- scanoss/api/semgrep/__init__.py +17 -17
- scanoss/api/semgrep/v2/__init__.py +17 -17
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +2359 -370
- scanoss/components.py +187 -94
- scanoss/constants.py +22 -0
- scanoss/cryptography.py +308 -0
- scanoss/csvoutput.py +91 -58
- scanoss/cyclonedx.py +221 -63
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/data/scanoss-settings-schema.json +254 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +582 -0
- scanoss/filecount.py +75 -69
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -0
- scanoss/inspection/__init__.py +23 -0
- scanoss/inspection/policy_check/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
- scanoss/inspection/policy_check/policy_check.py +222 -0
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/policy_check/scanoss/copyleft.py +243 -0
- scanoss/inspection/policy_check/scanoss/undeclared_component.py +309 -0
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/summary/license_summary.py +191 -0
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +123 -0
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/utils/scan_result_processor.py +417 -0
- scanoss/osadl.py +125 -0
- scanoss/results.py +275 -0
- scanoss/scancodedeps.py +87 -38
- scanoss/scanner.py +431 -539
- scanoss/scanners/__init__.py +23 -0
- scanoss/scanners/container_scanner.py +476 -0
- scanoss/scanners/folder_hasher.py +358 -0
- scanoss/scanners/scanner_config.py +73 -0
- scanoss/scanners/scanner_hfh.py +252 -0
- scanoss/scanoss_settings.py +337 -0
- scanoss/scanossapi.py +140 -101
- scanoss/scanossbase.py +59 -22
- scanoss/scanossgrpc.py +799 -251
- scanoss/scanpostprocessor.py +294 -0
- scanoss/scantype.py +22 -21
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +532 -174
- scanoss/threadeddependencies.py +148 -47
- scanoss/threadedscanning.py +53 -37
- scanoss/utils/__init__.py +23 -0
- scanoss/utils/abstract_presenter.py +103 -0
- scanoss/utils/crc64.py +96 -0
- scanoss/utils/file.py +84 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/utils/simhash.py +198 -0
- scanoss/winnowing.py +241 -63
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/METADATA +18 -9
- scanoss-1.43.1.dist-info/RECORD +110 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/WHEEL +1 -1
- scanoss-1.12.2.dist-info/RECORD +0 -58
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info/licenses}/LICENSE +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from typing import List
|
|
28
|
+
|
|
29
|
+
from ...policy_check.policy_check import PolicyCheck, PolicyOutput, PolicyStatus
|
|
30
|
+
from ...utils.markdown_utils import generate_jira_table, generate_table
|
|
31
|
+
from ...utils.scan_result_processor import ScanResultProcessor
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class License:
|
|
36
|
+
spdxid: str
|
|
37
|
+
copyleft: bool
|
|
38
|
+
url: str
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class Component:
|
|
42
|
+
purl: str
|
|
43
|
+
version: str
|
|
44
|
+
licenses: List[License]
|
|
45
|
+
status: str
|
|
46
|
+
|
|
47
|
+
class UndeclaredComponent(PolicyCheck[Component]):
|
|
48
|
+
"""
|
|
49
|
+
SCANOSS UndeclaredComponent class
|
|
50
|
+
Inspects for undeclared components
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__( # noqa: PLR0913
|
|
54
|
+
self,
|
|
55
|
+
debug: bool = False,
|
|
56
|
+
trace: bool = False,
|
|
57
|
+
quiet: bool = False,
|
|
58
|
+
filepath: str = None,
|
|
59
|
+
format_type: str = 'json',
|
|
60
|
+
status: str = None,
|
|
61
|
+
output: str = None,
|
|
62
|
+
sbom_format: str = 'settings'
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Initialize the UndeclaredComponent class.
|
|
66
|
+
|
|
67
|
+
:param debug: Enable debug mode
|
|
68
|
+
:param trace: Enable trace mode (default True)
|
|
69
|
+
:param quiet: Enable quiet mode
|
|
70
|
+
:param filepath: Path to the file containing component data
|
|
71
|
+
:param format_type: Output format ('json' or 'md')
|
|
72
|
+
:param status: Path to save status output
|
|
73
|
+
:param output: Path to save detailed output
|
|
74
|
+
:param sbom_format: Sbom format for status output (default 'settings')
|
|
75
|
+
"""
|
|
76
|
+
super().__init__(
|
|
77
|
+
debug, trace, quiet, format_type, status, name='Undeclared Components Policy', output=output
|
|
78
|
+
)
|
|
79
|
+
self.filepath = filepath
|
|
80
|
+
self.output = output
|
|
81
|
+
self.status = status
|
|
82
|
+
self.sbom_format = sbom_format
|
|
83
|
+
self.results_processor = ScanResultProcessor(self.debug, self.trace, self.quiet, self.filepath)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _get_undeclared_components(self, components: list[Component]) -> list or None:
|
|
87
|
+
"""
|
|
88
|
+
Filter the components list to include only undeclared components.
|
|
89
|
+
|
|
90
|
+
:param components: List of all components
|
|
91
|
+
:return: List of undeclared components
|
|
92
|
+
"""
|
|
93
|
+
if components is None:
|
|
94
|
+
self.print_debug('WARNING: No components provided!')
|
|
95
|
+
return None
|
|
96
|
+
undeclared_components = []
|
|
97
|
+
for component in components:
|
|
98
|
+
if component['status'] == 'pending':
|
|
99
|
+
# Remove unused keys
|
|
100
|
+
del component['count']
|
|
101
|
+
del component['declared']
|
|
102
|
+
del component['undeclared']
|
|
103
|
+
for lic in component['licenses']:
|
|
104
|
+
lic.pop('count', None) # None is default value if key doesn't exist
|
|
105
|
+
lic.pop('source', None) # None is default value if key doesn't exist
|
|
106
|
+
undeclared_components.append(component)
|
|
107
|
+
# end component loop
|
|
108
|
+
return undeclared_components
|
|
109
|
+
|
|
110
|
+
def _get_jira_summary(self, components: list[Component]) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Get a summary of the undeclared components.
|
|
113
|
+
|
|
114
|
+
:param components: List of all components
|
|
115
|
+
:return: Component summary markdown
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
Get a summary of the undeclared components.
|
|
120
|
+
|
|
121
|
+
:param components: List of all components
|
|
122
|
+
:return: Component summary markdown
|
|
123
|
+
"""
|
|
124
|
+
if len(components) > 0:
|
|
125
|
+
json_content = json.dumps(self._generate_scanoss_file(components), indent=2)
|
|
126
|
+
|
|
127
|
+
if self.sbom_format == 'settings':
|
|
128
|
+
return (
|
|
129
|
+
f'{len(components)} undeclared component(s) were found.\n'
|
|
130
|
+
f'Add the following snippet into your `scanoss.json` file\n'
|
|
131
|
+
f'{{code:json}}\n'
|
|
132
|
+
f'{json_content}\n'
|
|
133
|
+
f'{{code}}\n'
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
return (
|
|
137
|
+
f'{len(components)} undeclared component(s) were found.\n'
|
|
138
|
+
f'Add the following snippet into your `sbom.json` file\n'
|
|
139
|
+
f'{{code:json}}\n'
|
|
140
|
+
f'{json_content}\n'
|
|
141
|
+
f'{{code}}\n'
|
|
142
|
+
)
|
|
143
|
+
return f'{len(components)} undeclared component(s) were found.\\n'
|
|
144
|
+
|
|
145
|
+
def _get_summary(self, components: list) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Get a summary of the undeclared components.
|
|
148
|
+
|
|
149
|
+
:param components: List of all components
|
|
150
|
+
:return: Component summary markdown
|
|
151
|
+
"""
|
|
152
|
+
summary = f'{len(components)} undeclared component(s) were found.\n'
|
|
153
|
+
if len(components) > 0:
|
|
154
|
+
if self.sbom_format == 'settings':
|
|
155
|
+
summary += (
|
|
156
|
+
f'Add the following snippet into your `scanoss.json` file\n'
|
|
157
|
+
f'\n```json\n{json.dumps(self._generate_scanoss_file(components), indent=2)}\n```\n'
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
summary += (
|
|
161
|
+
f'Add the following snippet into your `sbom.json` file\n'
|
|
162
|
+
f'\n```json\n{json.dumps(self._generate_sbom_file(components), indent=2)}\n```\n'
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return summary
|
|
166
|
+
|
|
167
|
+
def _json(self, components: list[Component]) -> PolicyOutput:
|
|
168
|
+
"""
|
|
169
|
+
Format the undeclared components as JSON.
|
|
170
|
+
|
|
171
|
+
:param components: List of undeclared components
|
|
172
|
+
:return: Dictionary with formatted JSON details and summary
|
|
173
|
+
"""
|
|
174
|
+
# Use component grouped by licenses to generate the summary
|
|
175
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
176
|
+
details = {}
|
|
177
|
+
if len(components) > 0:
|
|
178
|
+
details = {'components': components}
|
|
179
|
+
return PolicyOutput(
|
|
180
|
+
details=f'{json.dumps(details, indent=2)}\n',
|
|
181
|
+
summary=self._get_summary(component_licenses)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _markdown(self, components: list[Component]) -> PolicyOutput:
|
|
185
|
+
"""
|
|
186
|
+
Format the undeclared components as Markdown.
|
|
187
|
+
|
|
188
|
+
:param components: List of undeclared components
|
|
189
|
+
:return: Dictionary with formatted Markdown details and summary
|
|
190
|
+
"""
|
|
191
|
+
headers = ['Component', 'License']
|
|
192
|
+
rows = []
|
|
193
|
+
# TODO look at using SpdxLite license name lookup method
|
|
194
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
195
|
+
for component in component_licenses:
|
|
196
|
+
rows.append([component.get('purl'), component.get('spdxid')])
|
|
197
|
+
return PolicyOutput(
|
|
198
|
+
details= f'### Undeclared components\n{generate_table(headers, rows)}\n',
|
|
199
|
+
summary= self._get_summary(component_licenses),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def _jira_markdown(self, components: list) -> PolicyOutput:
|
|
203
|
+
"""
|
|
204
|
+
Format the undeclared components as Markdown.
|
|
205
|
+
|
|
206
|
+
:param components: List of undeclared components
|
|
207
|
+
:return: Dictionary with formatted Markdown details and summary
|
|
208
|
+
"""
|
|
209
|
+
headers = ['Component', 'License']
|
|
210
|
+
rows = []
|
|
211
|
+
# TODO look at using SpdxLite license name lookup method
|
|
212
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
213
|
+
for component in component_licenses:
|
|
214
|
+
rows.append([component.get('purl'), component.get('spdxid')])
|
|
215
|
+
return PolicyOutput(
|
|
216
|
+
details= f'{generate_jira_table(headers, rows)}',
|
|
217
|
+
summary= self._get_jira_summary(component_licenses),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _get_unique_components(self, components: list) -> list:
|
|
221
|
+
"""
|
|
222
|
+
Generate a list of unique components.
|
|
223
|
+
|
|
224
|
+
:param components: List of undeclared components
|
|
225
|
+
:return: list of unique components
|
|
226
|
+
"""
|
|
227
|
+
unique_components = {}
|
|
228
|
+
if components is None:
|
|
229
|
+
self.print_stderr('WARNING: No components provided!')
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
for component in components:
|
|
233
|
+
unique_components[component['purl']] = {'purl': component['purl']}
|
|
234
|
+
return list(unique_components.values())
|
|
235
|
+
|
|
236
|
+
def _generate_scanoss_file(self, components: list) -> dict:
|
|
237
|
+
"""
|
|
238
|
+
Generate a list of PURLs for the scanoss.json file.
|
|
239
|
+
|
|
240
|
+
:param components: List of undeclared components
|
|
241
|
+
:return: scanoss.json Dictionary
|
|
242
|
+
"""
|
|
243
|
+
scanoss_settings = {
|
|
244
|
+
'bom': {
|
|
245
|
+
'include': self._get_unique_components(components),
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return scanoss_settings
|
|
250
|
+
|
|
251
|
+
def _generate_sbom_file(self, components: list) -> dict:
|
|
252
|
+
"""
|
|
253
|
+
Generate a list of PURLs for the SBOM file.
|
|
254
|
+
|
|
255
|
+
:param components: List of undeclared components
|
|
256
|
+
:return: SBOM Dictionary with components
|
|
257
|
+
"""
|
|
258
|
+
sbom = {
|
|
259
|
+
'components': self._get_unique_components(components),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return sbom
|
|
263
|
+
|
|
264
|
+
def _get_components(self):
|
|
265
|
+
"""
|
|
266
|
+
Extract and process components from file results only.
|
|
267
|
+
|
|
268
|
+
This method performs the following steps:
|
|
269
|
+
1. Validates if `self.results` is loaded. Returns `None` if not loaded.
|
|
270
|
+
2. Extracts file and snippet components into a dictionary.
|
|
271
|
+
3. Converts the components dictionary into a list of components.
|
|
272
|
+
4. Processes the licenses for each component by converting them into a list.
|
|
273
|
+
|
|
274
|
+
:return: A list of processed components with their licenses, or `None` if `self.results` is not set.
|
|
275
|
+
"""
|
|
276
|
+
if self.results_processor.get_results() is None:
|
|
277
|
+
return None
|
|
278
|
+
components: dict = {}
|
|
279
|
+
# Extract file and snippet components
|
|
280
|
+
components = self.results_processor.get_components_data(components)
|
|
281
|
+
# Convert to list and process licenses
|
|
282
|
+
return self.results_processor.convert_components_to_list(components)
|
|
283
|
+
|
|
284
|
+
def run(self):
|
|
285
|
+
"""
|
|
286
|
+
Run the undeclared component inspection process.
|
|
287
|
+
|
|
288
|
+
This method performs the following steps:
|
|
289
|
+
1. Get all components
|
|
290
|
+
2. Filter undeclared components
|
|
291
|
+
3. Format the results
|
|
292
|
+
4. Save the output to files if required
|
|
293
|
+
|
|
294
|
+
:return: Dictionary containing the inspection results
|
|
295
|
+
"""
|
|
296
|
+
self._debug()
|
|
297
|
+
components = self._get_components()
|
|
298
|
+
if components is None:
|
|
299
|
+
return PolicyStatus.ERROR.value, {}
|
|
300
|
+
# Get an undeclared component summary (if any)
|
|
301
|
+
undeclared_components = self._get_undeclared_components(components)
|
|
302
|
+
if undeclared_components is None:
|
|
303
|
+
return PolicyStatus.ERROR.value, {}
|
|
304
|
+
self.print_debug(f'Undeclared components: {undeclared_components}')
|
|
305
|
+
# Format the results and save to files if required
|
|
306
|
+
return self._generate_formatter_report(undeclared_components)
|
|
307
|
+
#
|
|
308
|
+
# End of UndeclaredComponent Class
|
|
309
|
+
#
|
|
File without changes
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
24
|
+
import json
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from ...scanossbase import ScanossBase
|
|
28
|
+
from ..policy_check.policy_check import T
|
|
29
|
+
from ..utils.scan_result_processor import ScanResultProcessor
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ComponentSummary(ScanossBase):
|
|
33
|
+
|
|
34
|
+
def __init__( # noqa: PLR0913
|
|
35
|
+
self,
|
|
36
|
+
debug: bool = False,
|
|
37
|
+
trace: bool = False,
|
|
38
|
+
quiet: bool = False,
|
|
39
|
+
filepath: str = None,
|
|
40
|
+
format_type: str = 'json',
|
|
41
|
+
output: str = None,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Initialize the ComponentSummary class.
|
|
45
|
+
|
|
46
|
+
:param debug: Enable debug mode
|
|
47
|
+
:param trace: Enable trace mode
|
|
48
|
+
:param quiet: Enable quiet mode
|
|
49
|
+
:param filepath: Path to the file containing component data
|
|
50
|
+
:param format_type: Output format ('json' or 'md')
|
|
51
|
+
"""
|
|
52
|
+
super().__init__(debug, trace, quiet)
|
|
53
|
+
self.filepath = filepath
|
|
54
|
+
self.output = output
|
|
55
|
+
self.results_processor = ScanResultProcessor(debug, trace, quiet, filepath)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _json(self, data: dict[str,Any]) -> dict[str,Any]:
|
|
59
|
+
"""
|
|
60
|
+
Format component summary data as JSON.
|
|
61
|
+
|
|
62
|
+
This method returns the component summary data in its original JSON structure
|
|
63
|
+
without any transformation. The data can be directly serialized to JSON format.
|
|
64
|
+
|
|
65
|
+
:param data: Dictionary containing component summary information including:
|
|
66
|
+
- components: List of component-license pairs with status and metadata
|
|
67
|
+
- totalComponents: Total number of unique components
|
|
68
|
+
- undeclaredComponents: Number of components with 'pending' status
|
|
69
|
+
- declaredComponents: Number of components with 'identified' status
|
|
70
|
+
- totalFilesDetected: Total count of files where components were detected
|
|
71
|
+
- totalFilesUndeclared: Count of files with undeclared components
|
|
72
|
+
- totalFilesDeclared: Count of files with declared components
|
|
73
|
+
:return: The same data dictionary, ready for JSON serialization
|
|
74
|
+
"""
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
def _markdown(self, data: list[T]) -> dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Format component summary data as Markdown (not yet implemented).
|
|
80
|
+
|
|
81
|
+
This method is intended to convert component summary data into a human-readable
|
|
82
|
+
Markdown format with tables and formatted sections.
|
|
83
|
+
|
|
84
|
+
:param data: List of component summary items to format
|
|
85
|
+
:return: Dictionary containing formatted Markdown output
|
|
86
|
+
"""
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
def _jira_markdown(self, data: list[T]) -> dict[str, Any]:
|
|
90
|
+
"""
|
|
91
|
+
Format component summary data as Jira-flavored Markdown (not yet implemented).
|
|
92
|
+
|
|
93
|
+
This method is intended to convert component summary data into Jira-compatible
|
|
94
|
+
Markdown format, which may include Jira-specific syntax for tables and formatting.
|
|
95
|
+
|
|
96
|
+
:param data: List of component summary items to format
|
|
97
|
+
:return: Dictionary containing Jira-formatted Markdown output
|
|
98
|
+
"""
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
def _get_component_summary_from_components(self, scan_components: list)-> dict:
|
|
102
|
+
"""
|
|
103
|
+
Get a component summary from detected components.
|
|
104
|
+
|
|
105
|
+
:param scan_components: List of all components
|
|
106
|
+
:return: Dict with license summary information
|
|
107
|
+
"""
|
|
108
|
+
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
109
|
+
component_licenses = self.results_processor.group_components_by_license(scan_components)
|
|
110
|
+
total_components = len(component_licenses)
|
|
111
|
+
# Get undeclared components
|
|
112
|
+
undeclared_components = len([c for c in component_licenses if c['status'] == 'pending'])
|
|
113
|
+
|
|
114
|
+
components: list = []
|
|
115
|
+
total_undeclared_files = 0
|
|
116
|
+
total_files_detected = 0
|
|
117
|
+
for component in scan_components:
|
|
118
|
+
total_files_detected += component['count']
|
|
119
|
+
total_undeclared_files += component['undeclared']
|
|
120
|
+
components.append({
|
|
121
|
+
'purl': component['purl'],
|
|
122
|
+
'version': component['version'],
|
|
123
|
+
'count': component['count'],
|
|
124
|
+
'undeclared': component['undeclared'],
|
|
125
|
+
'declared': component['count'] - component['undeclared'],
|
|
126
|
+
})
|
|
127
|
+
## End for loop components
|
|
128
|
+
return {
|
|
129
|
+
"components": component_licenses,
|
|
130
|
+
'totalComponents': total_components,
|
|
131
|
+
'undeclaredComponents': undeclared_components,
|
|
132
|
+
'declaredComponents': total_components - undeclared_components,
|
|
133
|
+
'totalFilesDetected': total_files_detected,
|
|
134
|
+
'totalFilesUndeclared': total_undeclared_files,
|
|
135
|
+
'totalFilesDeclared': total_files_detected - total_undeclared_files,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
def _get_components(self):
|
|
139
|
+
"""
|
|
140
|
+
Extract and process components from results and their dependencies.
|
|
141
|
+
|
|
142
|
+
This method performs the following steps:
|
|
143
|
+
1. Validates that `self.results` is loaded. Returns `None` if not.
|
|
144
|
+
2. Extracts file, snippet, and dependency components into a dictionary.
|
|
145
|
+
3. Converts components to a list and processes their licenses.
|
|
146
|
+
|
|
147
|
+
:return: A list of processed components with license data, or `None` if `self.results` is not set.
|
|
148
|
+
"""
|
|
149
|
+
if self.results_processor.get_results() is None:
|
|
150
|
+
raise ValueError(f'Error: No results found in {self.filepath}')
|
|
151
|
+
|
|
152
|
+
components: dict = {}
|
|
153
|
+
# Extract component and license data from file and dependency results. Both helpers mutate `components`
|
|
154
|
+
self.results_processor.get_components_data(components)
|
|
155
|
+
return self.results_processor.convert_components_to_list(components)
|
|
156
|
+
|
|
157
|
+
def _format(self, component_summary) -> str:
|
|
158
|
+
# TODO: Implement formatter to support dynamic outputs
|
|
159
|
+
json_data = self._json(component_summary)
|
|
160
|
+
return json.dumps(json_data, indent=2)
|
|
161
|
+
|
|
162
|
+
def run(self):
|
|
163
|
+
components = self._get_components()
|
|
164
|
+
component_summary = self._get_component_summary_from_components(components)
|
|
165
|
+
output = self._format(component_summary)
|
|
166
|
+
self.print_to_file_or_stdout(output, self.output)
|
|
167
|
+
return component_summary
|
|
168
|
+
#
|
|
169
|
+
# End of ComponentSummary Class
|
|
170
|
+
#
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from ...scanossbase import ScanossBase
|
|
29
|
+
from ..policy_check.policy_check import T
|
|
30
|
+
from ..utils.scan_result_processor import ScanResultProcessor
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LicenseSummary(ScanossBase):
|
|
34
|
+
"""
|
|
35
|
+
SCANOSS LicenseSummary class
|
|
36
|
+
Inspects results and generates comprehensive license summaries from detected components.
|
|
37
|
+
|
|
38
|
+
This class processes component scan results to extract, validate, and aggregate license
|
|
39
|
+
information, providing detailed summaries including copyleft analysis and license statistics.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Define required license fields as class constants
|
|
43
|
+
REQUIRED_LICENSE_FIELDS = ['spdxid', 'url', 'copyleft', 'source']
|
|
44
|
+
|
|
45
|
+
def __init__( # noqa: PLR0913
|
|
46
|
+
self,
|
|
47
|
+
debug: bool = False,
|
|
48
|
+
trace: bool = False,
|
|
49
|
+
quiet: bool = False,
|
|
50
|
+
filepath: str = None,
|
|
51
|
+
status: str = None,
|
|
52
|
+
output: str = None,
|
|
53
|
+
include: str = None,
|
|
54
|
+
exclude: str = None,
|
|
55
|
+
explicit: str = None,
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Initialize the LicenseSummary class.
|
|
59
|
+
|
|
60
|
+
:param debug: Enable debug mode
|
|
61
|
+
:param trace: Enable trace mode
|
|
62
|
+
:param quiet: Enable quiet mode
|
|
63
|
+
:param filepath: Path to the file containing component data
|
|
64
|
+
:param output: Path to save detailed output
|
|
65
|
+
:param include: Licenses to include in the analysis
|
|
66
|
+
:param exclude: Licenses to exclude from the analysis
|
|
67
|
+
:param explicit: Explicitly defined licenses
|
|
68
|
+
"""
|
|
69
|
+
super().__init__(debug=debug, trace=trace, quiet=quiet)
|
|
70
|
+
self.results_processor = ScanResultProcessor(debug, trace, quiet, filepath, include, exclude, explicit)
|
|
71
|
+
self.filepath = filepath
|
|
72
|
+
self.output = output
|
|
73
|
+
self.status = status
|
|
74
|
+
self.include = include
|
|
75
|
+
self.exclude = exclude
|
|
76
|
+
self.explicit = explicit
|
|
77
|
+
|
|
78
|
+
def _json(self, data: dict[str,Any]) -> dict[str, Any]:
|
|
79
|
+
"""
|
|
80
|
+
Format license summary data as JSON.
|
|
81
|
+
|
|
82
|
+
This method is intended to return the license summary data in JSON structure
|
|
83
|
+
for serialization. The data should include license information with copyleft
|
|
84
|
+
analysis and license statistics.
|
|
85
|
+
|
|
86
|
+
:param data: List of license summary items to format
|
|
87
|
+
:return: Dictionary containing license summary information including:
|
|
88
|
+
- licenses: List of detected licenses with SPDX IDs, URLs, and copyleft status
|
|
89
|
+
- detectedLicenses: Total number of unique licenses
|
|
90
|
+
- detectedLicensesWithCopyleft: Count of licenses marked as copyleft
|
|
91
|
+
"""
|
|
92
|
+
return data
|
|
93
|
+
|
|
94
|
+
def _markdown(self, data: list[T]) -> dict[str, Any]:
|
|
95
|
+
"""
|
|
96
|
+
Format license summary data as Markdown (not yet implemented).
|
|
97
|
+
|
|
98
|
+
This method is intended to convert license summary data into a human-readable
|
|
99
|
+
Markdown format with tables and formatted sections.
|
|
100
|
+
|
|
101
|
+
:param data: List of license summary items to format
|
|
102
|
+
:return: Dictionary containing formatted Markdown output
|
|
103
|
+
"""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
def _jira_markdown(self, data: list[T]) -> dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
Format license summary data as Jira-flavored Markdown (not yet implemented).
|
|
109
|
+
|
|
110
|
+
This method is intended to convert license summary data into Jira-compatible
|
|
111
|
+
Markdown format, which may include Jira-specific syntax for tables and formatting.
|
|
112
|
+
|
|
113
|
+
:param data: List of license summary items to format
|
|
114
|
+
:return: Dictionary containing Jira-formatted Markdown output
|
|
115
|
+
"""
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _get_licenses_summary_from_components(self, components: list)-> dict:
|
|
120
|
+
"""
|
|
121
|
+
Get a license summary from detected components.
|
|
122
|
+
|
|
123
|
+
:param components: List of all components
|
|
124
|
+
:return: Dict with license summary information
|
|
125
|
+
"""
|
|
126
|
+
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
127
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
128
|
+
license_component_count = {}
|
|
129
|
+
# Count license per component
|
|
130
|
+
for lic in component_licenses:
|
|
131
|
+
if lic['spdxid'] not in license_component_count:
|
|
132
|
+
license_component_count[lic['spdxid']] = 1
|
|
133
|
+
else:
|
|
134
|
+
license_component_count[lic['spdxid']] += 1
|
|
135
|
+
licenses:dict = {}
|
|
136
|
+
for comp_lic in component_licenses:
|
|
137
|
+
spdxid = comp_lic.get("spdxid")
|
|
138
|
+
url = comp_lic.get("url")
|
|
139
|
+
copyleft = comp_lic.get("copyleft")
|
|
140
|
+
if spdxid not in licenses:
|
|
141
|
+
licenses[spdxid] = {
|
|
142
|
+
'spdxid': spdxid,
|
|
143
|
+
'url': url,
|
|
144
|
+
'copyleft': copyleft,
|
|
145
|
+
'componentCount': license_component_count.get(spdxid, 0), # Append component count to license
|
|
146
|
+
}
|
|
147
|
+
## End for loop licenses
|
|
148
|
+
## End for loop components
|
|
149
|
+
detected_licenses = list(licenses.values())
|
|
150
|
+
licenses_with_copyleft = [lic for lic in detected_licenses if lic['copyleft']]
|
|
151
|
+
return {
|
|
152
|
+
'licenses': detected_licenses,
|
|
153
|
+
'detectedLicenses': len(detected_licenses), # Count unique licenses. SPDXID is considered unique
|
|
154
|
+
'detectedLicensesWithCopyleft': len(licenses_with_copyleft),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _get_components(self):
|
|
159
|
+
"""
|
|
160
|
+
Extract and process components from results and their dependencies.
|
|
161
|
+
|
|
162
|
+
This method performs the following steps:
|
|
163
|
+
1. Validates that `self.results` is loaded. Returns `None` if not.
|
|
164
|
+
2. Extracts file, snippet, and dependency components into a dictionary.
|
|
165
|
+
3. Converts components to a list and processes their licenses.
|
|
166
|
+
|
|
167
|
+
:return: A list of processed components with license data, or `None` if `self.results` is not set.
|
|
168
|
+
"""
|
|
169
|
+
if self.results_processor.get_results() is None:
|
|
170
|
+
raise ValueError(f'Error: No results found in {self.filepath}')
|
|
171
|
+
|
|
172
|
+
components: dict = {}
|
|
173
|
+
# Extract component and license data from file and dependency results. Both helpers mutate `components`
|
|
174
|
+
self.results_processor.get_components_data(components)
|
|
175
|
+
self.results_processor.get_dependencies_data(components)
|
|
176
|
+
return self.results_processor.convert_components_to_list(components)
|
|
177
|
+
|
|
178
|
+
def _format(self, license_summary) -> str:
|
|
179
|
+
# TODO: Implement formatter to support dynamic outputs
|
|
180
|
+
json_data = self._json(license_summary)
|
|
181
|
+
return json.dumps(json_data, indent=2)
|
|
182
|
+
|
|
183
|
+
def run(self):
|
|
184
|
+
components = self._get_components()
|
|
185
|
+
license_summary = self._get_licenses_summary_from_components(components)
|
|
186
|
+
output = self._format(license_summary)
|
|
187
|
+
self.print_to_file_or_stdout(output, self.output)
|
|
188
|
+
return license_summary
|
|
189
|
+
#
|
|
190
|
+
# End of LicenseSummary Class
|
|
191
|
+
#
|