scanoss 1.27.1__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/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 +1 -1
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -22
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- 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 -47
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +650 -33
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -37
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +64 -12
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +74 -31
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +252 -13
- 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/v2/scanoss_scanning_pb2.py +32 -21
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +1000 -186
- scanoss/components.py +80 -50
- scanoss/constants.py +7 -1
- scanoss/cryptography.py +89 -55
- scanoss/csvoutput.py +13 -7
- scanoss/cyclonedx.py +141 -9
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +2 -163
- scanoss/filecount.py +37 -38
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -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.py → policy_check/policy_check.py} +65 -72
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/{copyleft.py → policy_check/scanoss/copyleft.py} +89 -73
- scanoss/inspection/{undeclared_component.py → policy_check/scanoss/undeclared_component.py} +52 -46
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/{license_summary.py → summary/license_summary.py} +62 -12
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +57 -71
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/{inspect_base.py → utils/scan_result_processor.py} +53 -67
- scanoss/osadl.py +125 -0
- scanoss/scanner.py +135 -253
- scanoss/scanners/folder_hasher.py +47 -32
- scanoss/scanners/scanner_hfh.py +50 -18
- scanoss/scanoss_settings.py +33 -3
- scanoss/scanossapi.py +23 -25
- scanoss/scanossbase.py +1 -1
- scanoss/scanossgrpc.py +543 -289
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +11 -4
- scanoss/threadeddependencies.py +19 -18
- scanoss/threadedscanning.py +10 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/winnowing.py +71 -19
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/METADATA +8 -5
- scanoss-1.43.1.dist-info/RECORD +110 -0
- scanoss/inspection/component_summary.py +0 -94
- scanoss-1.27.1.dist-info/RECORD +0 -87
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/WHEEL +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
|
@@ -22,12 +22,12 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
from abc import abstractmethod
|
|
25
|
+
from abc import ABC, abstractmethod
|
|
26
26
|
from enum import Enum
|
|
27
|
-
from typing import
|
|
27
|
+
from typing import Callable, Dict, Generic, List, NamedTuple, TypeVar
|
|
28
28
|
|
|
29
|
-
from
|
|
30
|
-
from
|
|
29
|
+
from ...scanossbase import ScanossBase
|
|
30
|
+
from ..utils.license_utils import LicenseUtil
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class PolicyStatus(Enum):
|
|
@@ -35,19 +35,24 @@ class PolicyStatus(Enum):
|
|
|
35
35
|
Enumeration representing the status of a policy check.
|
|
36
36
|
|
|
37
37
|
Attributes:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ERROR (int): Indicates that an error occurred during the policy check (value:
|
|
38
|
+
POLICY_SUCCESS (int): Indicates that the policy check passed successfully (value: 0).
|
|
39
|
+
POLICY_FAIL (int): Indicates that the policy check failed (value: 2).
|
|
40
|
+
ERROR (int): Indicates that an error occurred during the policy check (value: 1).
|
|
41
41
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
ERROR = 2
|
|
42
|
+
POLICY_SUCCESS = 0
|
|
43
|
+
POLICY_FAIL = 2
|
|
44
|
+
ERROR = 1
|
|
46
45
|
#
|
|
47
46
|
# End of PolicyStatus Class
|
|
48
47
|
#
|
|
49
48
|
|
|
50
|
-
class
|
|
49
|
+
class PolicyOutput(NamedTuple):
|
|
50
|
+
details: str
|
|
51
|
+
summary: str
|
|
52
|
+
|
|
53
|
+
T = TypeVar('T')
|
|
54
|
+
|
|
55
|
+
class PolicyCheck(ScanossBase, Generic[T], ABC):
|
|
51
56
|
"""
|
|
52
57
|
A base class for implementing various software policy checks.
|
|
53
58
|
|
|
@@ -66,22 +71,20 @@ class PolicyCheck(InspectBase):
|
|
|
66
71
|
debug: bool = False,
|
|
67
72
|
trace: bool = False,
|
|
68
73
|
quiet: bool = False,
|
|
69
|
-
filepath: str = None,
|
|
70
74
|
format_type: str = None,
|
|
71
75
|
status: str = None,
|
|
72
|
-
output: str = None,
|
|
73
76
|
name: str = None,
|
|
77
|
+
output: str = None,
|
|
74
78
|
):
|
|
75
|
-
super().__init__(debug, trace, quiet
|
|
79
|
+
super().__init__(debug, trace, quiet)
|
|
76
80
|
self.license_util = LicenseUtil()
|
|
77
|
-
self.filepath = filepath
|
|
78
81
|
self.name = name
|
|
79
82
|
self.format_type = format_type
|
|
80
83
|
self.status = status
|
|
81
|
-
self.
|
|
84
|
+
self.output = output
|
|
82
85
|
|
|
83
86
|
@abstractmethod
|
|
84
|
-
def run(self):
|
|
87
|
+
def run(self)-> tuple[int,PolicyOutput]:
|
|
85
88
|
"""
|
|
86
89
|
Execute the policy check process.
|
|
87
90
|
|
|
@@ -92,95 +95,53 @@ class PolicyCheck(InspectBase):
|
|
|
92
95
|
3. Formatting the results
|
|
93
96
|
4. Saving the output to files if required
|
|
94
97
|
|
|
95
|
-
:return: A tuple containing:
|
|
98
|
+
:return: A named tuple containing two elements:
|
|
96
99
|
- First element: PolicyStatus enum value (SUCCESS, FAIL, or ERROR)
|
|
97
|
-
- Second element:
|
|
100
|
+
- Second element: PolicyOutput A tuple containing the policy results.
|
|
98
101
|
"""
|
|
99
102
|
pass
|
|
100
103
|
|
|
101
104
|
@abstractmethod
|
|
102
|
-
def _json(self,
|
|
105
|
+
def _json(self, data: list[T]) -> PolicyOutput:
|
|
103
106
|
"""
|
|
104
107
|
Format the policy checks results as JSON.
|
|
105
108
|
This method should be implemented by subclasses to create a Markdown representation
|
|
106
109
|
of the policy check results.
|
|
107
110
|
|
|
108
|
-
:param
|
|
111
|
+
:param data: List of data to be formatted.
|
|
109
112
|
:return: A dictionary containing two keys:
|
|
110
|
-
- '
|
|
113
|
+
- 'results': A JSON-formatted string with the full list of components
|
|
111
114
|
- 'summary': A string summarizing the number of components found
|
|
112
115
|
"""
|
|
113
116
|
pass
|
|
114
117
|
|
|
115
118
|
@abstractmethod
|
|
116
|
-
def _markdown(self,
|
|
119
|
+
def _markdown(self, data: list[T]) -> PolicyOutput:
|
|
117
120
|
"""
|
|
118
121
|
Generate Markdown output for the policy check results.
|
|
119
122
|
|
|
120
123
|
This method should be implemented by subclasses to create a Markdown representation
|
|
121
124
|
of the policy check results.
|
|
122
125
|
|
|
123
|
-
:param
|
|
126
|
+
:param data: List of data to be included in the output.
|
|
124
127
|
:return: A dictionary representing the Markdown output.
|
|
125
128
|
"""
|
|
126
129
|
pass
|
|
127
130
|
|
|
128
131
|
@abstractmethod
|
|
129
|
-
def _jira_markdown(self,
|
|
132
|
+
def _jira_markdown(self, data: list[T]) -> PolicyOutput:
|
|
130
133
|
"""
|
|
131
134
|
Generate Markdown output for the policy check results.
|
|
132
135
|
|
|
133
136
|
This method should be implemented by subclasses to create a Markdown representation
|
|
134
137
|
of the policy check results.
|
|
135
138
|
|
|
136
|
-
:param
|
|
139
|
+
:param data: List of data to be included in the output.
|
|
137
140
|
:return: A dictionary representing the Markdown output.
|
|
138
141
|
"""
|
|
139
142
|
pass
|
|
140
143
|
|
|
141
|
-
def
|
|
142
|
-
"""
|
|
143
|
-
Generate a Markdown table.
|
|
144
|
-
|
|
145
|
-
:param headers: List of headers for the table.
|
|
146
|
-
:param rows: List of rows for the table.
|
|
147
|
-
:param centered_columns: List of column indices to be centered.
|
|
148
|
-
:return: A string representing the Markdown table.
|
|
149
|
-
"""
|
|
150
|
-
col_sep = ' | '
|
|
151
|
-
centered_column_set = set(centered_columns or [])
|
|
152
|
-
if headers is None:
|
|
153
|
-
self.print_stderr('ERROR: Header are no set')
|
|
154
|
-
return None
|
|
155
|
-
|
|
156
|
-
# Decide which separator to use
|
|
157
|
-
def create_separator(index):
|
|
158
|
-
if centered_columns is None:
|
|
159
|
-
return '-'
|
|
160
|
-
return ':-:' if index in centered_column_set else '-'
|
|
161
|
-
|
|
162
|
-
# Build the row separator
|
|
163
|
-
row_separator = col_sep + col_sep.join(create_separator(index) for index, _ in enumerate(headers)) + col_sep
|
|
164
|
-
# build table rows
|
|
165
|
-
table_rows = [col_sep + col_sep.join(headers) + col_sep, row_separator]
|
|
166
|
-
table_rows.extend(col_sep + col_sep.join(row) + col_sep for row in rows)
|
|
167
|
-
return '\n'.join(table_rows)
|
|
168
|
-
|
|
169
|
-
def generate_jira_table(self, headers, rows, centered_columns=None):
|
|
170
|
-
col_sep = '*|*'
|
|
171
|
-
if headers is None:
|
|
172
|
-
self.print_stderr('ERROR: Header are no set')
|
|
173
|
-
return None
|
|
174
|
-
|
|
175
|
-
table_header = '|*' + col_sep.join(headers) + '*|\n'
|
|
176
|
-
table = table_header
|
|
177
|
-
for row in rows:
|
|
178
|
-
if len(headers) == len(row):
|
|
179
|
-
table += '|' + '|'.join(row) + '|\n'
|
|
180
|
-
|
|
181
|
-
return table
|
|
182
|
-
|
|
183
|
-
def _get_formatter(self) -> Callable[[List[dict]], Dict[str, Any]] or None:
|
|
144
|
+
def _get_formatter(self) -> Callable[[List[dict]], PolicyOutput]:
|
|
184
145
|
"""
|
|
185
146
|
Get the appropriate formatter function based on the specified format.
|
|
186
147
|
|
|
@@ -188,7 +149,7 @@ class PolicyCheck(InspectBase):
|
|
|
188
149
|
"""
|
|
189
150
|
valid_format = self._is_valid_format()
|
|
190
151
|
if not valid_format:
|
|
191
|
-
|
|
152
|
+
raise ValueError('Invalid format specified')
|
|
192
153
|
# a map of which format function to return
|
|
193
154
|
function_map = {
|
|
194
155
|
'json': self._json,
|
|
@@ -208,7 +169,6 @@ class PolicyCheck(InspectBase):
|
|
|
208
169
|
self.print_stderr(f'Format: {self.format_type}')
|
|
209
170
|
self.print_stderr(f'Status: {self.status}')
|
|
210
171
|
self.print_stderr(f'Output: {self.output}')
|
|
211
|
-
self.print_stderr(f'Input: {self.filepath}')
|
|
212
172
|
|
|
213
173
|
def _is_valid_format(self) -> bool:
|
|
214
174
|
"""
|
|
@@ -224,6 +184,39 @@ class PolicyCheck(InspectBase):
|
|
|
224
184
|
self.print_stderr(f'ERROR: Invalid format "{self.format_type}". Valid formats are: {valid_formats_str}')
|
|
225
185
|
return False
|
|
226
186
|
return True
|
|
187
|
+
|
|
188
|
+
def _generate_formatter_report(self, components: list[Dict]):
|
|
189
|
+
"""
|
|
190
|
+
Generates a formatted report for a given component based on the defined formatter.
|
|
191
|
+
|
|
192
|
+
Parameters:
|
|
193
|
+
components (List[dict]): A list of dictionaries representing the components to be
|
|
194
|
+
processed and formatted. Each dictionary contains detailed information that adheres
|
|
195
|
+
to the format requirements for the specified formatter.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Tuple[int, dict]: A tuple where the first element represents the policy status code
|
|
199
|
+
and the second element is a dictionary containing formatted results information,
|
|
200
|
+
typically with keys 'details' and 'summary'.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
KeyError: When a required key is missing from the provided component, causing the
|
|
204
|
+
formatter to fail.
|
|
205
|
+
ValueError: If an invalid component is passed and renders unable to process.
|
|
206
|
+
"""
|
|
207
|
+
# Get a formatter for the output results
|
|
208
|
+
formatter = self._get_formatter()
|
|
209
|
+
if formatter is None:
|
|
210
|
+
return PolicyStatus.ERROR.value, {}
|
|
211
|
+
# Format the results
|
|
212
|
+
policy_output = formatter(components)
|
|
213
|
+
## Save outputs if required
|
|
214
|
+
self.print_to_file_or_stdout(policy_output.details, self.output)
|
|
215
|
+
self.print_to_file_or_stderr(policy_output.summary, self.status)
|
|
216
|
+
# Check to see if we have policy violations
|
|
217
|
+
if len(components) > 0:
|
|
218
|
+
return PolicyStatus.POLICY_FAIL.value, policy_output
|
|
219
|
+
return PolicyStatus.POLICY_SUCCESS.value, policy_output
|
|
227
220
|
#
|
|
228
221
|
# End of PolicyCheck Class
|
|
229
222
|
#
|
|
File without changes
|
|
@@ -23,12 +23,31 @@ SPDX-License-Identifier: MIT
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import json
|
|
26
|
-
from
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from typing import Dict, List
|
|
27
28
|
|
|
28
|
-
from .
|
|
29
|
+
from scanoss.constants import DEFAULT_COPYLEFT_LICENSE_SOURCES
|
|
29
30
|
|
|
31
|
+
from ...policy_check.policy_check import PolicyCheck, PolicyOutput, PolicyStatus
|
|
32
|
+
from ...utils.markdown_utils import generate_jira_table, generate_table
|
|
33
|
+
from ...utils.scan_result_processor import ScanResultProcessor
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class License:
|
|
38
|
+
spdxid: str
|
|
39
|
+
copyleft: bool
|
|
40
|
+
url: str
|
|
41
|
+
source: str
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Component:
|
|
45
|
+
purl: str
|
|
46
|
+
version: str
|
|
47
|
+
licenses: List[License]
|
|
48
|
+
status: str
|
|
49
|
+
|
|
50
|
+
class Copyleft(PolicyCheck[Component]):
|
|
32
51
|
"""
|
|
33
52
|
SCANOSS Copyleft class
|
|
34
53
|
Inspects components for copyleft licenses
|
|
@@ -46,9 +65,10 @@ class Copyleft(PolicyCheck):
|
|
|
46
65
|
include: str = None,
|
|
47
66
|
exclude: str = None,
|
|
48
67
|
explicit: str = None,
|
|
68
|
+
license_sources: list = None,
|
|
49
69
|
):
|
|
50
70
|
"""
|
|
51
|
-
|
|
71
|
+
Initialise the Copyleft class.
|
|
52
72
|
|
|
53
73
|
:param debug: Enable debug mode
|
|
54
74
|
:param trace: Enable trace mode (default True)
|
|
@@ -60,18 +80,27 @@ class Copyleft(PolicyCheck):
|
|
|
60
80
|
:param include: Licenses to include in the analysis
|
|
61
81
|
:param exclude: Licenses to exclude from the analysis
|
|
62
82
|
:param explicit: Explicitly defined licenses
|
|
83
|
+
:param license_sources: List of license sources to check
|
|
63
84
|
"""
|
|
64
|
-
super().__init__(
|
|
85
|
+
super().__init__(
|
|
86
|
+
debug, trace, quiet, format_type, status, name='Copyleft Policy', output=output
|
|
87
|
+
)
|
|
65
88
|
self.license_util.init(include, exclude, explicit)
|
|
66
89
|
self.filepath = filepath
|
|
67
|
-
self.format = format
|
|
68
90
|
self.output = output
|
|
69
91
|
self.status = status
|
|
70
|
-
self.
|
|
71
|
-
self.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
self.license_sources = license_sources or DEFAULT_COPYLEFT_LICENSE_SOURCES
|
|
93
|
+
self.results_processor = ScanResultProcessor(
|
|
94
|
+
self.debug,
|
|
95
|
+
self.trace,
|
|
96
|
+
self.quiet,
|
|
97
|
+
self.filepath,
|
|
98
|
+
include,
|
|
99
|
+
exclude,
|
|
100
|
+
explicit,
|
|
101
|
+
self.license_sources)
|
|
102
|
+
|
|
103
|
+
def _json(self, components: list[Component]) -> PolicyOutput:
|
|
75
104
|
"""
|
|
76
105
|
Format the components with copyleft licenses as JSON.
|
|
77
106
|
|
|
@@ -79,70 +108,71 @@ class Copyleft(PolicyCheck):
|
|
|
79
108
|
:return: Dictionary with formatted JSON details and summary
|
|
80
109
|
"""
|
|
81
110
|
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
82
|
-
component_licenses = self.
|
|
111
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
83
112
|
details = {}
|
|
84
113
|
if len(components) > 0:
|
|
85
114
|
details = {'components': components}
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
return PolicyOutput(
|
|
116
|
+
details= f'{json.dumps(details, indent=2)}\n',
|
|
117
|
+
summary= f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
|
|
118
|
+
)
|
|
90
119
|
|
|
91
|
-
def _markdown(self, components: list) ->
|
|
120
|
+
def _markdown(self, components: list[Component]) -> PolicyOutput:
|
|
92
121
|
"""
|
|
93
122
|
Format the components with copyleft licenses as Markdown.
|
|
94
123
|
|
|
95
124
|
:param components: List of components with copyleft licenses
|
|
96
125
|
:return: Dictionary with formatted Markdown details and summary
|
|
97
126
|
"""
|
|
98
|
-
|
|
99
|
-
component_licenses = self._group_components_by_license(components)
|
|
100
|
-
headers = ['Component', 'License', 'URL', 'Copyleft']
|
|
101
|
-
centered_columns = [1, 4]
|
|
102
|
-
rows: [[]] = []
|
|
103
|
-
for comp_lic_item in component_licenses:
|
|
104
|
-
row = [
|
|
105
|
-
comp_lic_item['purl'],
|
|
106
|
-
comp_lic_item['spdxid'],
|
|
107
|
-
comp_lic_item['url'],
|
|
108
|
-
'YES' if comp_lic_item['copyleft'] else 'NO',
|
|
109
|
-
]
|
|
110
|
-
rows.append(row)
|
|
111
|
-
# End license loop
|
|
112
|
-
# End component loop
|
|
113
|
-
return {
|
|
114
|
-
'details': f'### Copyleft licenses\n{self.generate_table(headers, rows, centered_columns)}\n',
|
|
115
|
-
'summary': f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
|
|
116
|
-
}
|
|
127
|
+
return self._md_summary_generator(components, generate_table)
|
|
117
128
|
|
|
118
|
-
def _jira_markdown(self, components: list) ->
|
|
129
|
+
def _jira_markdown(self, components: list[Component]) -> PolicyOutput:
|
|
119
130
|
"""
|
|
120
131
|
Format the components with copyleft licenses as Markdown.
|
|
121
132
|
|
|
122
133
|
:param components: List of components with copyleft licenses
|
|
123
134
|
:return: Dictionary with formatted Markdown details and summary
|
|
124
135
|
"""
|
|
136
|
+
return self._md_summary_generator(components, generate_jira_table)
|
|
137
|
+
|
|
138
|
+
def _md_summary_generator(self, components: list[Component], table_generator) -> PolicyOutput:
|
|
139
|
+
"""
|
|
140
|
+
Generates a Markdown summary for components with a focus on copyleft licenses.
|
|
141
|
+
|
|
142
|
+
This function processes a list of components and groups them by their licenses.
|
|
143
|
+
For each group, the components are mapped with their license data and a tabular representation is created.
|
|
144
|
+
The generated Markdown summary includes a detailed table and a summary overview.
|
|
145
|
+
|
|
146
|
+
Parameters:
|
|
147
|
+
components: list[Component]
|
|
148
|
+
A list of Component objects to process for generating the summary.
|
|
149
|
+
table_generator
|
|
150
|
+
A callable function to generate tabular data for components.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
PolicyOutput
|
|
154
|
+
"""
|
|
125
155
|
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
126
|
-
component_licenses = self.
|
|
156
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
127
157
|
headers = ['Component', 'License', 'URL', 'Copyleft']
|
|
128
158
|
centered_columns = [1, 4]
|
|
129
|
-
rows
|
|
159
|
+
rows = []
|
|
130
160
|
for comp_lic_item in component_licenses:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
161
|
+
row = [
|
|
162
|
+
comp_lic_item['purl'],
|
|
163
|
+
comp_lic_item['spdxid'],
|
|
164
|
+
comp_lic_item['url'],
|
|
165
|
+
'YES' if comp_lic_item['copyleft'] else 'NO',
|
|
166
|
+
]
|
|
167
|
+
rows.append(row)
|
|
168
|
+
# End license loop
|
|
139
169
|
# End component loop
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
return PolicyOutput(
|
|
171
|
+
details= f'### Copyleft Licenses\n{table_generator(headers, rows, centered_columns)}',
|
|
172
|
+
summary= f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
|
|
173
|
+
)
|
|
144
174
|
|
|
145
|
-
def
|
|
175
|
+
def _get_components_with_copyleft_licenses(self, components: list) -> list[Dict]:
|
|
146
176
|
"""
|
|
147
177
|
Filter the components list to include only those with copyleft licenses.
|
|
148
178
|
|
|
@@ -179,14 +209,13 @@ class Copyleft(PolicyCheck):
|
|
|
179
209
|
|
|
180
210
|
:return: A list of processed components with license data, or `None` if `self.results` is not set.
|
|
181
211
|
"""
|
|
182
|
-
if self.
|
|
212
|
+
if self.results_processor.get_results() is None:
|
|
183
213
|
return None
|
|
184
|
-
|
|
185
214
|
components: dict = {}
|
|
186
215
|
# Extract component and license data from file and dependency results. Both helpers mutate `components`
|
|
187
|
-
self.
|
|
188
|
-
self.
|
|
189
|
-
return self.
|
|
216
|
+
self.results_processor.get_components_data(components)
|
|
217
|
+
self.results_processor.get_dependencies_data(components)
|
|
218
|
+
return self.results_processor.convert_components_to_list(components)
|
|
190
219
|
|
|
191
220
|
def run(self):
|
|
192
221
|
"""
|
|
@@ -206,22 +235,9 @@ class Copyleft(PolicyCheck):
|
|
|
206
235
|
if components is None:
|
|
207
236
|
return PolicyStatus.ERROR.value, {}
|
|
208
237
|
# Get a list of copyleft components if they exist
|
|
209
|
-
copyleft_components = self.
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
if formatter is None:
|
|
213
|
-
return PolicyStatus.ERROR.value, {}
|
|
214
|
-
# Format the results
|
|
215
|
-
results = formatter(copyleft_components)
|
|
216
|
-
## Save outputs if required
|
|
217
|
-
self.print_to_file_or_stdout(results['details'], self.output)
|
|
218
|
-
self.print_to_file_or_stderr(results['summary'], self.status)
|
|
219
|
-
# Check to see if we have policy violations
|
|
220
|
-
if len(copyleft_components) <= 0:
|
|
221
|
-
return PolicyStatus.FAIL.value, results
|
|
222
|
-
return PolicyStatus.SUCCESS.value, results
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
copyleft_components = self._get_components_with_copyleft_licenses(components)
|
|
239
|
+
# Format the results and save to files if required
|
|
240
|
+
return self._generate_formatter_report(copyleft_components)
|
|
225
241
|
#
|
|
226
242
|
# End of Copyleft Class
|
|
227
243
|
#
|
|
@@ -23,12 +23,28 @@ SPDX-License-Identifier: MIT
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import json
|
|
26
|
-
from
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from typing import List
|
|
27
28
|
|
|
28
|
-
from .policy_check import PolicyCheck, PolicyStatus
|
|
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
|
|
29
32
|
|
|
30
33
|
|
|
31
|
-
|
|
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]):
|
|
32
48
|
"""
|
|
33
49
|
SCANOSS UndeclaredComponent class
|
|
34
50
|
Inspects for undeclared components
|
|
@@ -43,7 +59,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
43
59
|
format_type: str = 'json',
|
|
44
60
|
status: str = None,
|
|
45
61
|
output: str = None,
|
|
46
|
-
sbom_format: str = 'settings'
|
|
62
|
+
sbom_format: str = 'settings'
|
|
47
63
|
):
|
|
48
64
|
"""
|
|
49
65
|
Initialize the UndeclaredComponent class.
|
|
@@ -58,15 +74,16 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
58
74
|
:param sbom_format: Sbom format for status output (default 'settings')
|
|
59
75
|
"""
|
|
60
76
|
super().__init__(
|
|
61
|
-
debug, trace, quiet,
|
|
77
|
+
debug, trace, quiet, format_type, status, name='Undeclared Components Policy', output=output
|
|
62
78
|
)
|
|
63
79
|
self.filepath = filepath
|
|
64
|
-
self.format = format
|
|
65
80
|
self.output = output
|
|
66
81
|
self.status = status
|
|
67
82
|
self.sbom_format = sbom_format
|
|
83
|
+
self.results_processor = ScanResultProcessor(self.debug, self.trace, self.quiet, self.filepath)
|
|
84
|
+
|
|
68
85
|
|
|
69
|
-
def
|
|
86
|
+
def _get_undeclared_components(self, components: list[Component]) -> list or None:
|
|
70
87
|
"""
|
|
71
88
|
Filter the components list to include only undeclared components.
|
|
72
89
|
|
|
@@ -90,7 +107,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
90
107
|
# end component loop
|
|
91
108
|
return undeclared_components
|
|
92
109
|
|
|
93
|
-
def _get_jira_summary(self, components: list) -> str:
|
|
110
|
+
def _get_jira_summary(self, components: list[Component]) -> str:
|
|
94
111
|
"""
|
|
95
112
|
Get a summary of the undeclared components.
|
|
96
113
|
|
|
@@ -147,7 +164,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
147
164
|
|
|
148
165
|
return summary
|
|
149
166
|
|
|
150
|
-
def _json(self, components: list) ->
|
|
167
|
+
def _json(self, components: list[Component]) -> PolicyOutput:
|
|
151
168
|
"""
|
|
152
169
|
Format the undeclared components as JSON.
|
|
153
170
|
|
|
@@ -155,16 +172,16 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
155
172
|
:return: Dictionary with formatted JSON details and summary
|
|
156
173
|
"""
|
|
157
174
|
# Use component grouped by licenses to generate the summary
|
|
158
|
-
component_licenses = self.
|
|
175
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
159
176
|
details = {}
|
|
160
177
|
if len(components) > 0:
|
|
161
178
|
details = {'components': components}
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
return PolicyOutput(
|
|
180
|
+
details=f'{json.dumps(details, indent=2)}\n',
|
|
181
|
+
summary=self._get_summary(component_licenses)
|
|
182
|
+
)
|
|
166
183
|
|
|
167
|
-
def _markdown(self, components: list) ->
|
|
184
|
+
def _markdown(self, components: list[Component]) -> PolicyOutput:
|
|
168
185
|
"""
|
|
169
186
|
Format the undeclared components as Markdown.
|
|
170
187
|
|
|
@@ -172,17 +189,17 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
172
189
|
:return: Dictionary with formatted Markdown details and summary
|
|
173
190
|
"""
|
|
174
191
|
headers = ['Component', 'License']
|
|
175
|
-
rows
|
|
192
|
+
rows = []
|
|
176
193
|
# TODO look at using SpdxLite license name lookup method
|
|
177
|
-
component_licenses = self.
|
|
194
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
178
195
|
for component in component_licenses:
|
|
179
196
|
rows.append([component.get('purl'), component.get('spdxid')])
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
197
|
+
return PolicyOutput(
|
|
198
|
+
details= f'### Undeclared components\n{generate_table(headers, rows)}\n',
|
|
199
|
+
summary= self._get_summary(component_licenses),
|
|
200
|
+
)
|
|
184
201
|
|
|
185
|
-
def _jira_markdown(self, components: list) ->
|
|
202
|
+
def _jira_markdown(self, components: list) -> PolicyOutput:
|
|
186
203
|
"""
|
|
187
204
|
Format the undeclared components as Markdown.
|
|
188
205
|
|
|
@@ -190,15 +207,15 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
190
207
|
:return: Dictionary with formatted Markdown details and summary
|
|
191
208
|
"""
|
|
192
209
|
headers = ['Component', 'License']
|
|
193
|
-
rows
|
|
210
|
+
rows = []
|
|
194
211
|
# TODO look at using SpdxLite license name lookup method
|
|
195
|
-
component_licenses = self.
|
|
212
|
+
component_licenses = self.results_processor.group_components_by_license(components)
|
|
196
213
|
for component in component_licenses:
|
|
197
214
|
rows.append([component.get('purl'), component.get('spdxid')])
|
|
198
|
-
return
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
215
|
+
return PolicyOutput(
|
|
216
|
+
details= f'{generate_jira_table(headers, rows)}',
|
|
217
|
+
summary= self._get_jira_summary(component_licenses),
|
|
218
|
+
)
|
|
202
219
|
|
|
203
220
|
def _get_unique_components(self, components: list) -> list:
|
|
204
221
|
"""
|
|
@@ -256,13 +273,13 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
256
273
|
|
|
257
274
|
:return: A list of processed components with their licenses, or `None` if `self.results` is not set.
|
|
258
275
|
"""
|
|
259
|
-
if self.
|
|
276
|
+
if self.results_processor.get_results() is None:
|
|
260
277
|
return None
|
|
261
278
|
components: dict = {}
|
|
262
279
|
# Extract file and snippet components
|
|
263
|
-
components = self.
|
|
280
|
+
components = self.results_processor.get_components_data(components)
|
|
264
281
|
# Convert to list and process licenses
|
|
265
|
-
return self.
|
|
282
|
+
return self.results_processor.convert_components_to_list(components)
|
|
266
283
|
|
|
267
284
|
def run(self):
|
|
268
285
|
"""
|
|
@@ -280,24 +297,13 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
280
297
|
components = self._get_components()
|
|
281
298
|
if components is None:
|
|
282
299
|
return PolicyStatus.ERROR.value, {}
|
|
283
|
-
# Get undeclared component summary (if any)
|
|
284
|
-
undeclared_components = self.
|
|
300
|
+
# Get an undeclared component summary (if any)
|
|
301
|
+
undeclared_components = self._get_undeclared_components(components)
|
|
285
302
|
if undeclared_components is None:
|
|
286
303
|
return PolicyStatus.ERROR.value, {}
|
|
287
304
|
self.print_debug(f'Undeclared components: {undeclared_components}')
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return PolicyStatus.ERROR.value, {}
|
|
291
|
-
results = formatter(undeclared_components)
|
|
292
|
-
# Output the results
|
|
293
|
-
self.print_to_file_or_stdout(results['details'], self.output)
|
|
294
|
-
self.print_to_file_or_stderr(results['summary'], self.status)
|
|
295
|
-
# Determine if the filter found results or not
|
|
296
|
-
if len(undeclared_components) <= 0:
|
|
297
|
-
return PolicyStatus.FAIL.value, results
|
|
298
|
-
return PolicyStatus.SUCCESS.value, results
|
|
299
|
-
|
|
300
|
-
|
|
305
|
+
# Format the results and save to files if required
|
|
306
|
+
return self._generate_formatter_report(undeclared_components)
|
|
301
307
|
#
|
|
302
308
|
# End of UndeclaredComponent Class
|
|
303
309
|
#
|
|
File without changes
|