scanoss 1.17.4__py3-none-any.whl → 1.18.0__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.
- scanoss/__init__.py +1 -1
- scanoss/cli.py +3 -1
- scanoss/data/build_date.txt +1 -1
- scanoss/inspection/undeclared_component.py +41 -11
- scanoss/scanner.py +11 -9
- scanoss/scanoss_settings.py +56 -22
- scanoss/scanpostprocessor.py +185 -61
- {scanoss-1.17.4.dist-info → scanoss-1.18.0.dist-info}/METADATA +2 -1
- {scanoss-1.17.4.dist-info → scanoss-1.18.0.dist-info}/RECORD +13 -13
- {scanoss-1.17.4.dist-info → scanoss-1.18.0.dist-info}/WHEEL +1 -1
- {scanoss-1.17.4.dist-info → scanoss-1.18.0.dist-info}/LICENSE +0 -0
- {scanoss-1.17.4.dist-info → scanoss-1.18.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.17.4.dist-info → scanoss-1.18.0.dist-info}/top_level.txt +0 -0
scanoss/__init__.py
CHANGED
scanoss/cli.py
CHANGED
|
@@ -315,6 +315,8 @@ def setup_args() -> None:
|
|
|
315
315
|
|
|
316
316
|
# Inspect Sub-command: inspect undeclared
|
|
317
317
|
p_undeclared = p_inspect_sub.add_parser('undeclared', aliases=['un'],description="Inspect for undeclared components", help='Inspect for undeclared components')
|
|
318
|
+
p_undeclared.add_argument('--sbom-format',required=False ,choices=['legacy', 'settings'],
|
|
319
|
+
default="settings",help='Sbom format for status output')
|
|
318
320
|
p_undeclared.set_defaults(func=inspect_undeclared)
|
|
319
321
|
|
|
320
322
|
for p in [p_copyleft, p_undeclared]:
|
|
@@ -858,7 +860,7 @@ def inspect_undeclared(parser, args):
|
|
|
858
860
|
open(status_output, 'w').close()
|
|
859
861
|
i_undeclared = UndeclaredComponent(debug=args.debug, trace=args.trace, quiet=args.quiet,
|
|
860
862
|
filepath=args.input, format_type=args.format,
|
|
861
|
-
status=status_output, output=output)
|
|
863
|
+
status=status_output, output=output, sbom_format=args.sbom_format)
|
|
862
864
|
status, _ = i_undeclared.run()
|
|
863
865
|
sys.exit(status)
|
|
864
866
|
|
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20241115114610, utime: 1731671170
|
|
@@ -32,7 +32,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False, filepath: str = None,
|
|
35
|
-
format_type: str = 'json', status: str = None, output: str = None):
|
|
35
|
+
format_type: str = 'json', status: str = None, output: str = None, sbom_format: str = 'settings'):
|
|
36
36
|
"""
|
|
37
37
|
Initialize the UndeclaredComponent class.
|
|
38
38
|
|
|
@@ -43,6 +43,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
43
43
|
:param format_type: Output format ('json' or 'md')
|
|
44
44
|
:param status: Path to save status output
|
|
45
45
|
:param output: Path to save detailed output
|
|
46
|
+
:param sbom_format: Sbom format for status output (default 'settings')
|
|
46
47
|
"""
|
|
47
48
|
super().__init__(debug, trace, quiet, filepath, format_type, status, output,
|
|
48
49
|
name='Undeclared Components Policy')
|
|
@@ -50,6 +51,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
50
51
|
self.format = format
|
|
51
52
|
self.output = output
|
|
52
53
|
self.status = status
|
|
54
|
+
self.sbom_format = sbom_format
|
|
53
55
|
|
|
54
56
|
def _get_undeclared_component(self, components: list)-> list or None:
|
|
55
57
|
"""
|
|
@@ -59,7 +61,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
59
61
|
:return: List of undeclared components
|
|
60
62
|
"""
|
|
61
63
|
if components is None:
|
|
62
|
-
self.
|
|
64
|
+
self.print_debug(f'WARNING: No components provided!')
|
|
63
65
|
return None
|
|
64
66
|
undeclared_components = []
|
|
65
67
|
for component in components:
|
|
@@ -78,9 +80,14 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
78
80
|
"""
|
|
79
81
|
summary = f'{len(components)} undeclared component(s) were found.\n'
|
|
80
82
|
if len(components) > 0:
|
|
83
|
+
if self.sbom_format == 'settings':
|
|
84
|
+
summary += (f'Add the following snippet into your `scanoss.json` file\n'
|
|
85
|
+
f'\n```json\n{json.dumps(self._generate_scanoss_file(components), indent=2)}\n```\n')
|
|
86
|
+
return summary
|
|
87
|
+
|
|
81
88
|
summary += (f'Add the following snippet into your `sbom.json` file\n'
|
|
82
89
|
f'\n```json\n{json.dumps(self._generate_sbom_file(components), indent=2)}\n```\n')
|
|
83
|
-
|
|
90
|
+
return summary
|
|
84
91
|
|
|
85
92
|
def _json(self, components: list) -> Dict[str, Any]:
|
|
86
93
|
"""
|
|
@@ -115,23 +122,46 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
115
122
|
'summary': self._get_summary(components),
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
def
|
|
125
|
+
def _get_unique_components(self, components: list) -> list:
|
|
119
126
|
"""
|
|
120
|
-
Generate a list of
|
|
127
|
+
Generate a list of unique components.
|
|
121
128
|
|
|
122
129
|
:param components: List of undeclared components
|
|
123
|
-
:return:
|
|
130
|
+
:return: list of unique components
|
|
124
131
|
"""
|
|
125
|
-
|
|
126
132
|
unique_components = {}
|
|
127
133
|
if components is None:
|
|
128
134
|
self.print_stderr(f'WARNING: No components provided!')
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
for component in components:
|
|
138
|
+
unique_components[component['purl']] = {'purl': component['purl']}
|
|
139
|
+
return list(unique_components.values())
|
|
140
|
+
|
|
141
|
+
def _generate_scanoss_file(self, components: list) -> dict:
|
|
142
|
+
"""
|
|
143
|
+
Generate a list of PURLs for the scanoss.json file.
|
|
144
|
+
|
|
145
|
+
:param components: List of undeclared components
|
|
146
|
+
:return: scanoss.json Dictionary
|
|
147
|
+
"""
|
|
148
|
+
scanoss_settings = {
|
|
149
|
+
'bom':{
|
|
150
|
+
'include': self._get_unique_components(components),
|
|
151
|
+
}
|
|
152
|
+
}
|
|
132
153
|
|
|
154
|
+
return scanoss_settings
|
|
155
|
+
|
|
156
|
+
def _generate_sbom_file(self, components: list) -> dict:
|
|
157
|
+
"""
|
|
158
|
+
Generate a list of PURLs for the SBOM file.
|
|
159
|
+
|
|
160
|
+
:param components: List of undeclared components
|
|
161
|
+
:return: SBOM Dictionary with components
|
|
162
|
+
"""
|
|
133
163
|
sbom = {
|
|
134
|
-
'components':
|
|
164
|
+
'components': self._get_unique_components(components),
|
|
135
165
|
}
|
|
136
166
|
|
|
137
167
|
return sbom
|
scanoss/scanner.py
CHANGED
|
@@ -161,12 +161,13 @@ class Scanner(ScanossBase):
|
|
|
161
161
|
if skip_extensions: # Append extra file extensions to skip
|
|
162
162
|
self.skip_extensions.extend(skip_extensions)
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
self._maybe_set_api_sbom()
|
|
164
|
+
self.scan_settings = scan_settings
|
|
165
|
+
self.post_processor = ScanPostProcessor(scan_settings, debug=debug, trace=trace, quiet=quiet) if scan_settings else None
|
|
166
|
+
self._maybe_set_api_sbom()
|
|
168
167
|
|
|
169
168
|
def _maybe_set_api_sbom(self):
|
|
169
|
+
if not self.scan_settings:
|
|
170
|
+
return
|
|
170
171
|
sbom = self.scan_settings.get_sbom()
|
|
171
172
|
if sbom:
|
|
172
173
|
self.scanoss_api.set_sbom(sbom)
|
|
@@ -521,11 +522,12 @@ class Scanner(ScanossBase):
|
|
|
521
522
|
success = False
|
|
522
523
|
dep_responses = self.threaded_deps.responses
|
|
523
524
|
|
|
524
|
-
raw_scan_results = self._merge_scan_results(
|
|
525
|
-
scan_responses, dep_responses, file_map
|
|
526
|
-
)
|
|
525
|
+
raw_scan_results = self._merge_scan_results(scan_responses, dep_responses, file_map)
|
|
527
526
|
|
|
528
|
-
|
|
527
|
+
if self.post_processor:
|
|
528
|
+
results = self.post_processor.load_results(raw_scan_results).post_process()
|
|
529
|
+
else:
|
|
530
|
+
results = raw_scan_results
|
|
529
531
|
|
|
530
532
|
if self.output_format == 'plain':
|
|
531
533
|
self.__log_result(json.dumps(results, indent=2, sort_keys=True))
|
|
@@ -564,7 +566,7 @@ class Scanner(ScanossBase):
|
|
|
564
566
|
for dep_file in dep_files:
|
|
565
567
|
file = dep_file.pop("file", None)
|
|
566
568
|
if file:
|
|
567
|
-
results[file] = dep_file
|
|
569
|
+
results[file] = [dep_file]
|
|
568
570
|
|
|
569
571
|
return results
|
|
570
572
|
|
scanoss/scanoss_settings.py
CHANGED
|
@@ -69,15 +69,15 @@ class ScanossSettings(ScanossBase):
|
|
|
69
69
|
json_file = Path(filepath).resolve()
|
|
70
70
|
|
|
71
71
|
if not json_file.exists():
|
|
72
|
-
self.print_stderr(f
|
|
72
|
+
self.print_stderr(f'Scan settings file not found: {filepath}')
|
|
73
73
|
self.data = {}
|
|
74
74
|
|
|
75
|
-
with open(json_file,
|
|
76
|
-
self.print_debug(f
|
|
75
|
+
with open(json_file, 'r') as jsonfile:
|
|
76
|
+
self.print_debug(f'Loading scan settings from: {filepath}')
|
|
77
77
|
try:
|
|
78
78
|
self.data = json.load(jsonfile)
|
|
79
79
|
except Exception as e:
|
|
80
|
-
self.print_stderr(f
|
|
80
|
+
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
81
81
|
return self
|
|
82
82
|
|
|
83
83
|
def set_file_type(self, file_type: str):
|
|
@@ -91,9 +91,7 @@ class ScanossSettings(ScanossBase):
|
|
|
91
91
|
"""
|
|
92
92
|
self.settings_file_type = file_type
|
|
93
93
|
if not self._is_valid_sbom_file:
|
|
94
|
-
raise Exception(
|
|
95
|
-
'Invalid scan settings file, missing "components" or "bom")'
|
|
96
|
-
)
|
|
94
|
+
raise Exception('Invalid scan settings file, missing "components" or "bom")')
|
|
97
95
|
return self
|
|
98
96
|
|
|
99
97
|
def set_scan_type(self, scan_type: str):
|
|
@@ -111,7 +109,7 @@ class ScanossSettings(ScanossBase):
|
|
|
111
109
|
Returns:
|
|
112
110
|
bool: True if the file is valid, False otherwise
|
|
113
111
|
"""
|
|
114
|
-
if not self.data.get(
|
|
112
|
+
if not self.data.get('components') or not self.data.get('bom'):
|
|
115
113
|
return False
|
|
116
114
|
return True
|
|
117
115
|
|
|
@@ -122,14 +120,14 @@ class ScanossSettings(ScanossBase):
|
|
|
122
120
|
dict: If using scanoss.json
|
|
123
121
|
list: If using SBOM.json
|
|
124
122
|
"""
|
|
125
|
-
if self.settings_file_type ==
|
|
123
|
+
if self.settings_file_type == 'legacy':
|
|
126
124
|
if isinstance(self.data, list):
|
|
127
125
|
return self.data
|
|
128
|
-
elif isinstance(self.data, dict) and self.data.get(
|
|
129
|
-
return self.data.get(
|
|
126
|
+
elif isinstance(self.data, dict) and self.data.get('components'):
|
|
127
|
+
return self.data.get('components')
|
|
130
128
|
else:
|
|
131
129
|
return []
|
|
132
|
-
return self.data.get(
|
|
130
|
+
return self.data.get('bom', {})
|
|
133
131
|
|
|
134
132
|
def get_bom_include(self) -> List[BomEntry]:
|
|
135
133
|
"""Get the list of components to include in the scan
|
|
@@ -137,9 +135,9 @@ class ScanossSettings(ScanossBase):
|
|
|
137
135
|
Returns:
|
|
138
136
|
list: List of components to include in the scan
|
|
139
137
|
"""
|
|
140
|
-
if self.settings_file_type ==
|
|
138
|
+
if self.settings_file_type == 'legacy':
|
|
141
139
|
return self._get_bom()
|
|
142
|
-
return self._get_bom().get(
|
|
140
|
+
return self._get_bom().get('include', [])
|
|
143
141
|
|
|
144
142
|
def get_bom_remove(self) -> List[BomEntry]:
|
|
145
143
|
"""Get the list of components to remove from the scan
|
|
@@ -147,21 +145,31 @@ class ScanossSettings(ScanossBase):
|
|
|
147
145
|
Returns:
|
|
148
146
|
list: List of components to remove from the scan
|
|
149
147
|
"""
|
|
150
|
-
if self.settings_file_type ==
|
|
148
|
+
if self.settings_file_type == 'legacy':
|
|
151
149
|
return self._get_bom()
|
|
152
|
-
return self._get_bom().get(
|
|
150
|
+
return self._get_bom().get('remove', [])
|
|
151
|
+
|
|
152
|
+
def get_bom_replace(self) -> List[BomEntry]:
|
|
153
|
+
"""Get the list of components to replace in the scan
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
list: List of components to replace in the scan
|
|
157
|
+
"""
|
|
158
|
+
if self.settings_file_type == 'legacy':
|
|
159
|
+
return []
|
|
160
|
+
return self._get_bom().get('replace', [])
|
|
153
161
|
|
|
154
162
|
def get_sbom(self):
|
|
155
163
|
"""Get the SBOM to be sent to the SCANOSS API
|
|
156
164
|
|
|
157
165
|
Returns:
|
|
158
|
-
dict: SBOM
|
|
166
|
+
dict: SBOM request payload
|
|
159
167
|
"""
|
|
160
168
|
if not self.data:
|
|
161
169
|
return None
|
|
162
170
|
return {
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
'scan_type': self.scan_type,
|
|
172
|
+
'assets': json.dumps(self._get_sbom_assets()),
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
def _get_sbom_assets(self):
|
|
@@ -170,8 +178,15 @@ class ScanossSettings(ScanossBase):
|
|
|
170
178
|
Returns:
|
|
171
179
|
List: List of SBOM assets
|
|
172
180
|
"""
|
|
173
|
-
if self.scan_type ==
|
|
174
|
-
|
|
181
|
+
if self.scan_type == 'identify':
|
|
182
|
+
include_bom_entries = self._remove_duplicates(self.normalize_bom_entries(self.get_bom_include()))
|
|
183
|
+
replace_bom_entries = self._remove_duplicates(self.normalize_bom_entries(self.get_bom_replace()))
|
|
184
|
+
self.print_debug(
|
|
185
|
+
f"Scan type set to 'identify'. Adding {len(include_bom_entries) + len(replace_bom_entries)} components as context to the scan. \n"
|
|
186
|
+
f"From Include list: {[entry['purl'] for entry in include_bom_entries]} \n"
|
|
187
|
+
f"From Replace list: {[entry['purl'] for entry in replace_bom_entries]} \n"
|
|
188
|
+
)
|
|
189
|
+
return include_bom_entries + replace_bom_entries
|
|
175
190
|
return self.normalize_bom_entries(self.get_bom_remove())
|
|
176
191
|
|
|
177
192
|
@staticmethod
|
|
@@ -188,7 +203,26 @@ class ScanossSettings(ScanossBase):
|
|
|
188
203
|
for entry in bom_entries:
|
|
189
204
|
normalized_bom_entries.append(
|
|
190
205
|
{
|
|
191
|
-
|
|
206
|
+
'purl': entry.get('purl', ''),
|
|
192
207
|
}
|
|
193
208
|
)
|
|
194
209
|
return normalized_bom_entries
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _remove_duplicates(bom_entries: List[BomEntry]) -> List[BomEntry]:
|
|
213
|
+
"""Remove duplicate BOM entries
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
bom_entries (List[Dict]): List of BOM entries
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
List: List of unique BOM entries
|
|
220
|
+
"""
|
|
221
|
+
already_added = set()
|
|
222
|
+
unique_entries = []
|
|
223
|
+
for entry in bom_entries:
|
|
224
|
+
entry_tuple = tuple(entry.items())
|
|
225
|
+
if entry_tuple not in already_added:
|
|
226
|
+
already_added.add(entry_tuple)
|
|
227
|
+
unique_entries.append(entry)
|
|
228
|
+
return unique_entries
|
scanoss/scanpostprocessor.py
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2024, 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
23
|
"""
|
|
24
24
|
|
|
25
|
-
from typing import List
|
|
25
|
+
from typing import List, Tuple
|
|
26
|
+
|
|
27
|
+
from packageurl import PackageURL
|
|
28
|
+
from packageurl.contrib import purl2url
|
|
26
29
|
|
|
27
30
|
from .scanoss_settings import BomEntry, ScanossSettings
|
|
28
31
|
from .scanossbase import ScanossBase
|
|
@@ -49,7 +52,8 @@ class ScanPostProcessor(ScanossBase):
|
|
|
49
52
|
"""
|
|
50
53
|
super().__init__(debug, trace, quiet)
|
|
51
54
|
self.scan_settings = scan_settings
|
|
52
|
-
self.results = results
|
|
55
|
+
self.results: dict = results
|
|
56
|
+
self.component_info_map: dict = {}
|
|
53
57
|
|
|
54
58
|
def load_results(self, raw_results: dict):
|
|
55
59
|
"""Load the raw results
|
|
@@ -58,22 +62,32 @@ class ScanPostProcessor(ScanossBase):
|
|
|
58
62
|
raw_results (dict): Raw scan results
|
|
59
63
|
"""
|
|
60
64
|
self.results = raw_results
|
|
65
|
+
self._load_component_info()
|
|
61
66
|
return self
|
|
62
67
|
|
|
68
|
+
def _load_component_info(self):
|
|
69
|
+
"""Create a map of component information from scan results for faster lookup"""
|
|
70
|
+
if not self.results:
|
|
71
|
+
return
|
|
72
|
+
for _, result in self.results.items():
|
|
73
|
+
result = result[0] if isinstance(result, list) else result
|
|
74
|
+
purls = result.get('purl', [])
|
|
75
|
+
for purl in purls:
|
|
76
|
+
self.component_info_map[purl] = result
|
|
77
|
+
|
|
63
78
|
def post_process(self):
|
|
64
79
|
"""Post-process the scan results
|
|
65
80
|
|
|
66
81
|
Returns:
|
|
67
82
|
dict: Processed results
|
|
68
83
|
"""
|
|
69
|
-
self.
|
|
84
|
+
self._remove_dismissed_files()
|
|
85
|
+
self._replace_purls()
|
|
70
86
|
return self.results
|
|
71
87
|
|
|
72
|
-
def
|
|
88
|
+
def _remove_dismissed_files(self):
|
|
73
89
|
"""Remove entries from the results based on files and/or purls specified in the SCANOSS settings file"""
|
|
74
|
-
|
|
75
90
|
to_remove_entries = self.scan_settings.get_bom_remove()
|
|
76
|
-
|
|
77
91
|
if not to_remove_entries:
|
|
78
92
|
return
|
|
79
93
|
|
|
@@ -83,55 +97,165 @@ class ScanPostProcessor(ScanossBase):
|
|
|
83
97
|
if not self._should_remove_result(result_path, result, to_remove_entries)
|
|
84
98
|
}
|
|
85
99
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
def _replace_purls(self):
|
|
101
|
+
"""Replace purls in the results based on the SCANOSS settings file"""
|
|
102
|
+
to_replace_entries = self.scan_settings.get_bom_replace()
|
|
103
|
+
if not to_replace_entries:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
for result_path, result in self.results.items():
|
|
107
|
+
result = result[0] if isinstance(result, list) else result
|
|
108
|
+
should_replace, to_replace_with_purl = self._should_replace_result(result_path, result, to_replace_entries)
|
|
109
|
+
if should_replace:
|
|
110
|
+
self.results[result_path] = [self._update_replaced_result(result, to_replace_with_purl)]
|
|
111
|
+
|
|
112
|
+
def _update_replaced_result(self, result: dict, to_replace_with_purl: str) -> dict:
|
|
113
|
+
"""
|
|
114
|
+
Update the result with the new purl and component information if available,
|
|
115
|
+
otherwise removes the old component information
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
result (dict): The result to update
|
|
119
|
+
to_replace_with_purl (str): The purl to replace with
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
dict: Updated result
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
if self.component_info_map.get(to_replace_with_purl):
|
|
126
|
+
result.update(self.component_info_map[to_replace_with_purl])
|
|
127
|
+
else:
|
|
128
|
+
try:
|
|
129
|
+
new_component = PackageURL.from_string(to_replace_with_purl).to_dict()
|
|
130
|
+
new_component_url = purl2url.get_repo_url(to_replace_with_purl)
|
|
131
|
+
except Exception:
|
|
132
|
+
self.print_stderr(
|
|
133
|
+
f"Error while replacing: Invalid PURL '{to_replace_with_purl}' in settings file. Abort replacing."
|
|
134
|
+
)
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
result['component'] = new_component.get('name')
|
|
138
|
+
result['url'] = new_component_url
|
|
139
|
+
result['vendor'] = new_component.get('namespace')
|
|
140
|
+
|
|
141
|
+
result.pop('licenses', None)
|
|
142
|
+
result.pop('file', None)
|
|
143
|
+
result.pop('file_hash', None)
|
|
144
|
+
result.pop('file_url', None)
|
|
145
|
+
result.pop('latest', None)
|
|
146
|
+
result.pop('release_date', None)
|
|
147
|
+
result.pop('source_hash', None)
|
|
148
|
+
result.pop('url_hash', None)
|
|
149
|
+
result.pop('url_stats', None)
|
|
150
|
+
result.pop('url_stats', None)
|
|
151
|
+
result.pop('version', None)
|
|
152
|
+
|
|
153
|
+
result['purl'] = [to_replace_with_purl]
|
|
154
|
+
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
def _should_replace_result(
|
|
158
|
+
self, result_path: str, result: dict, to_replace_entries: List[BomEntry]
|
|
159
|
+
) -> Tuple[bool, str]:
|
|
160
|
+
"""Check if a result should be replaced based on the SCANOSS settings
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
result_path (str): Path of the result
|
|
164
|
+
result (dict): Result to check
|
|
165
|
+
to_replace_entries (List[BomEntry]): BOM entries to replace from the settings file
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
bool: True if the result should be replaced, False otherwise
|
|
169
|
+
str: The purl to replace with
|
|
170
|
+
"""
|
|
171
|
+
result_purls = result.get('purl', [])
|
|
172
|
+
for to_replace_entry in to_replace_entries:
|
|
173
|
+
to_replace_path = to_replace_entry.get('path')
|
|
174
|
+
to_replace_purl = to_replace_entry.get('purl')
|
|
175
|
+
to_replace_with = to_replace_entry.get('replace_with')
|
|
176
|
+
|
|
177
|
+
if not to_replace_path and not to_replace_purl or not to_replace_with:
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
self._is_full_match(result_path, result_purls, to_replace_entry)
|
|
182
|
+
or (not to_replace_path and to_replace_purl in result_purls)
|
|
183
|
+
or (not to_replace_purl and to_replace_path == result_path)
|
|
184
|
+
):
|
|
185
|
+
self._print_message(result_path, result_purls, to_replace_entry, 'Replacing')
|
|
186
|
+
return True, to_replace_with
|
|
187
|
+
|
|
188
|
+
return False, None
|
|
189
|
+
|
|
190
|
+
def _should_remove_result(self, result_path: str, result: dict, to_remove_entries: List[BomEntry]) -> bool:
|
|
89
191
|
"""Check if a result should be removed based on the SCANOSS settings"""
|
|
90
192
|
result = result[0] if isinstance(result, list) else result
|
|
91
|
-
result_purls = result.get(
|
|
193
|
+
result_purls = result.get('purl', [])
|
|
92
194
|
|
|
93
195
|
for to_remove_entry in to_remove_entries:
|
|
94
|
-
to_remove_path = to_remove_entry.get(
|
|
95
|
-
to_remove_purl = to_remove_entry.get(
|
|
196
|
+
to_remove_path = to_remove_entry.get('path')
|
|
197
|
+
to_remove_purl = to_remove_entry.get('purl')
|
|
96
198
|
|
|
97
199
|
if not to_remove_path and not to_remove_purl:
|
|
98
200
|
continue
|
|
99
201
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if not to_remove_path and to_remove_purl in result_purls:
|
|
107
|
-
self._print_removal_message(result_path, result_purls, to_remove_entry)
|
|
108
|
-
return True
|
|
109
|
-
|
|
110
|
-
# Bom entry has only path
|
|
111
|
-
if not to_remove_purl and to_remove_path == result_path:
|
|
112
|
-
self._print_removal_message(result_path, result_purls, to_remove_entry)
|
|
202
|
+
if (
|
|
203
|
+
self._is_full_match(result_path, result_purls, to_remove_entry)
|
|
204
|
+
or (not to_remove_path and to_remove_purl in result_purls)
|
|
205
|
+
or (not to_remove_purl and to_remove_path == result_path)
|
|
206
|
+
):
|
|
207
|
+
self._print_message(result_path, result_purls, to_remove_entry, 'Removing')
|
|
113
208
|
return True
|
|
114
209
|
|
|
115
210
|
return False
|
|
116
211
|
|
|
117
|
-
def
|
|
118
|
-
self,
|
|
212
|
+
def _print_message(
|
|
213
|
+
self,
|
|
214
|
+
result_path: str,
|
|
215
|
+
result_purls: List[str],
|
|
216
|
+
bom_entry: BomEntry,
|
|
217
|
+
action: str,
|
|
119
218
|
) -> None:
|
|
120
|
-
"""Print a message about removing a result"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
elif to_remove_entry.get("purl"):
|
|
124
|
-
message = f"Removing '{result_path}' from the results. Found PURL match."
|
|
125
|
-
else:
|
|
126
|
-
message = f"Removing '{result_path}' from the results. Found path match."
|
|
127
|
-
|
|
128
|
-
self.print_msg(
|
|
129
|
-
f"{message}\n"
|
|
219
|
+
"""Print a message about replacing or removing a result"""
|
|
220
|
+
message = (
|
|
221
|
+
f"{self._get_match_type_message(result_path, result_purls, bom_entry, action)} \n"
|
|
130
222
|
f"Details:\n"
|
|
131
223
|
f" - PURLs: {', '.join(result_purls)}\n"
|
|
132
224
|
f" - Path: '{result_path}'\n"
|
|
133
225
|
)
|
|
134
226
|
|
|
227
|
+
if action == 'Replacing':
|
|
228
|
+
message += f" - {action} with '{bom_entry.get('replace_with')}'"
|
|
229
|
+
|
|
230
|
+
self.print_debug(message)
|
|
231
|
+
|
|
232
|
+
def _get_match_type_message(
|
|
233
|
+
self,
|
|
234
|
+
result_path: str,
|
|
235
|
+
result_purls: List[str],
|
|
236
|
+
bom_entry: BomEntry,
|
|
237
|
+
action: str,
|
|
238
|
+
) -> str:
|
|
239
|
+
"""Compose message based on match type
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
result_path (str): Path of the scan result
|
|
243
|
+
result_purls (List[str]): Purls of the scan result
|
|
244
|
+
bom_entry (BomEntry): BOM entry to compare with
|
|
245
|
+
action (str): Post processing action being performed
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
str: The message to be printed
|
|
249
|
+
"""
|
|
250
|
+
if bom_entry.get('path') and bom_entry.get('purl'):
|
|
251
|
+
message = f"{action} '{result_path}'. Full match found."
|
|
252
|
+
elif bom_entry.get('purl'):
|
|
253
|
+
message = f"{action} '{result_path}'. Found PURL match."
|
|
254
|
+
else:
|
|
255
|
+
message = f"{action} '{result_path}'. Found path match."
|
|
256
|
+
|
|
257
|
+
return message
|
|
258
|
+
|
|
135
259
|
def _is_full_match(
|
|
136
260
|
self,
|
|
137
261
|
result_path: str,
|
|
@@ -153,7 +277,7 @@ class ScanPostProcessor(ScanossBase):
|
|
|
153
277
|
return False
|
|
154
278
|
|
|
155
279
|
return bool(
|
|
156
|
-
(bom_entry.get(
|
|
157
|
-
and (bom_entry.get(
|
|
158
|
-
and (bom_entry.get(
|
|
280
|
+
(bom_entry.get('purl') and bom_entry.get('path'))
|
|
281
|
+
and (bom_entry.get('path') == result_path)
|
|
282
|
+
and (bom_entry.get('purl') in result_purls)
|
|
159
283
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scanoss
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.0
|
|
4
4
|
Summary: Simple Python library to leverage the SCANOSS APIs
|
|
5
5
|
Home-page: https://scanoss.com
|
|
6
6
|
Author: SCANOSS
|
|
@@ -26,6 +26,7 @@ Requires-Dist: pypac
|
|
|
26
26
|
Requires-Dist: pyOpenSSL
|
|
27
27
|
Requires-Dist: google-api-core
|
|
28
28
|
Requires-Dist: importlib-resources
|
|
29
|
+
Requires-Dist: packageurl-python
|
|
29
30
|
Provides-Extra: fast_winnowing
|
|
30
31
|
Requires-Dist: scanoss-winnowing>=0.5.0; extra == "fast-winnowing"
|
|
31
32
|
|
|
@@ -4,20 +4,20 @@ protoc_gen_swagger/options/annotations_pb2.py,sha256=b25EDD6gssUWnFby9gxgcpLIROT
|
|
|
4
4
|
protoc_gen_swagger/options/annotations_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
5
5
|
protoc_gen_swagger/options/openapiv2_pb2.py,sha256=vYElGp8E1vGHszvWqX97zNG9GFJ7u2QcdK9ouq0XdyI,14939
|
|
6
6
|
protoc_gen_swagger/options/openapiv2_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
7
|
-
scanoss/__init__.py,sha256=
|
|
8
|
-
scanoss/cli.py,sha256=
|
|
7
|
+
scanoss/__init__.py,sha256=_jYIWkrW2Ht4mXs71uxI_j5ZKtzLx-vmW5npONyA9g4,1163
|
|
8
|
+
scanoss/cli.py,sha256=WS5Vqe0V59EBLyVl-l8PD-_RTCCUNIm2JdANk4X5DxI,50863
|
|
9
9
|
scanoss/components.py,sha256=ZHZ1KA69shxOASZK7USD9yPTITpAc_RXL5q5zpDK23o,12590
|
|
10
10
|
scanoss/csvoutput.py,sha256=hBwr_Fc6mBdOdXgyQcdFrockYH-PJ0jblowlExJ6OPg,9925
|
|
11
11
|
scanoss/cyclonedx.py,sha256=JVBYeR3D-i4yP9cVSyWvm0_7Y8Kr2MC5GxMgRGAf8R0,12585
|
|
12
12
|
scanoss/filecount.py,sha256=o7xb6m387ucnsU4H1OXGzf_AdWsudhAHe49T8uX4Ieo,6660
|
|
13
13
|
scanoss/results.py,sha256=7G33QAYYI9qI61TCzXjSLYXMmg5CDtZS5e2QhnQfE74,9883
|
|
14
14
|
scanoss/scancodedeps.py,sha256=_9d7MAV20-FrET7mF7gW-BZiz2eHrtwudgrEcSX0oZQ,11321
|
|
15
|
-
scanoss/scanner.py,sha256=
|
|
16
|
-
scanoss/scanoss_settings.py,sha256=
|
|
15
|
+
scanoss/scanner.py,sha256=iHDDaM4gzhYJN1pxN5VEvnynvTn78zMHlZ6kGInbNoo,51861
|
|
16
|
+
scanoss/scanoss_settings.py,sha256=zJO149YsLnjWOqxPlpdGQ1Q_gytQS1U6b29-y8qAMgI,7647
|
|
17
17
|
scanoss/scanossapi.py,sha256=TJxPctr-0DTn_26LfM__OAMfntaXzvheFTbdmU-5pnM,11953
|
|
18
18
|
scanoss/scanossbase.py,sha256=zMDRCLbrcoRvYEKQRuZXnBiVY4_Vsplmg_APbB65oaU,3084
|
|
19
19
|
scanoss/scanossgrpc.py,sha256=ythZkr6F0P0hl_KPYoHkos_IL97TxLKeYfAouX_CUnM,20491
|
|
20
|
-
scanoss/scanpostprocessor.py,sha256=
|
|
20
|
+
scanoss/scanpostprocessor.py,sha256=mUnH12otH9s5pEqCa9jzrd_aWcNAOR4PngvPa6gZtkQ,10758
|
|
21
21
|
scanoss/scantype.py,sha256=R2-ExLGOrYxaJFtIK2AEo2caD0XrN1zpF5q1qT9Zsyc,1326
|
|
22
22
|
scanoss/spdxlite.py,sha256=REChAWV-6qhp16jc4X2lMb1v7VvYiDH5nN9VDV3fDaQ,15828
|
|
23
23
|
scanoss/threadeddependencies.py,sha256=sOIAjiPTmxybKz2yhT4-ixXBeC4K8UQVq6JQj4e8mLc,9906
|
|
@@ -50,17 +50,17 @@ scanoss/api/vulnerabilities/__init__.py,sha256=FLQtiDiv85Q1Chk-sJ9ky9WOV1mulZhEK
|
|
|
50
50
|
scanoss/api/vulnerabilities/v2/__init__.py,sha256=FLQtiDiv85Q1Chk-sJ9ky9WOV1mulZhEKjiBihlwiaM,1139
|
|
51
51
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py,sha256=CFhF80av8tenGvn9AIsGEtRJPuV2dC_syA5JLZb2lDw,5464
|
|
52
52
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py,sha256=HlS4k4Zmx6RIAqaO9I96jD-eyF5yU6Xx04pVm7pdqOg,6864
|
|
53
|
-
scanoss/data/build_date.txt,sha256=
|
|
53
|
+
scanoss/data/build_date.txt,sha256=9ipmjb7WYIgf6aLKZmxtGRKvpBBytZ1IpVIg0m7LQo8,40
|
|
54
54
|
scanoss/data/spdx-exceptions.json,sha256=s7UTYxC7jqQXr11YBlIWYCNwN6lRDFTR33Y8rpN_dA4,17953
|
|
55
55
|
scanoss/data/spdx-licenses.json,sha256=A6Z0q82gaTLtnopBfzeIVZjJFxkdRW1g2TuumQc-lII,228794
|
|
56
56
|
scanoss/inspection/__init__.py,sha256=z62680zKq4OmBOugSODvgpSwdsloZL7bvcaMbnx3xgU,1139
|
|
57
57
|
scanoss/inspection/copyleft.py,sha256=dkiLkgNYz7cbIQZCzy6zThiIyHkqrper_xruZ9PQhAI,6563
|
|
58
58
|
scanoss/inspection/policy_check.py,sha256=eo5VfEBwKoDSqIwRi0xwaVLy6EUR29HlH5Bl0Kpvx7I,14752
|
|
59
|
-
scanoss/inspection/undeclared_component.py,sha256=
|
|
59
|
+
scanoss/inspection/undeclared_component.py,sha256=c2xwWyrPtQbdl0MgHTN_CpaJwC1PAofqyfYBSuWt_yI,7965
|
|
60
60
|
scanoss/inspection/utils/license_utils.py,sha256=mIaoVWXMA6shkRQmgmiP2mWchjxX4ex8LWs91Nf6rq4,5093
|
|
61
|
-
scanoss-1.
|
|
62
|
-
scanoss-1.
|
|
63
|
-
scanoss-1.
|
|
64
|
-
scanoss-1.
|
|
65
|
-
scanoss-1.
|
|
66
|
-
scanoss-1.
|
|
61
|
+
scanoss-1.18.0.dist-info/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
|
|
62
|
+
scanoss-1.18.0.dist-info/METADATA,sha256=oFubkhmg7cK4UVpmVN5-kbd4KDe3CPPgtqtfSK8IJRc,5969
|
|
63
|
+
scanoss-1.18.0.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
|
64
|
+
scanoss-1.18.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
|
|
65
|
+
scanoss-1.18.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
|
|
66
|
+
scanoss-1.18.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|