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.
Files changed (109) hide show
  1. protoc_gen_swagger/__init__.py +13 -13
  2. protoc_gen_swagger/options/__init__.py +13 -13
  3. protoc_gen_swagger/options/annotations_pb2.py +18 -12
  4. protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
  5. protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
  6. protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
  7. protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
  8. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
  9. scanoss/__init__.py +18 -18
  10. scanoss/api/__init__.py +17 -17
  11. scanoss/api/common/__init__.py +17 -17
  12. scanoss/api/common/v2/__init__.py +17 -17
  13. scanoss/api/common/v2/scanoss_common_pb2.py +49 -20
  14. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
  15. scanoss/api/components/__init__.py +17 -17
  16. scanoss/api/components/v2/__init__.py +17 -17
  17. scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
  18. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
  19. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -21
  20. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +766 -13
  21. scanoss/api/dependencies/__init__.py +17 -17
  22. scanoss/api/dependencies/v2/__init__.py +17 -17
  23. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -29
  24. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +94 -8
  25. scanoss/api/geoprovenance/__init__.py +23 -0
  26. scanoss/api/geoprovenance/v2/__init__.py +23 -0
  27. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +92 -0
  28. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +381 -0
  29. scanoss/api/licenses/__init__.py +23 -0
  30. scanoss/api/licenses/v2/__init__.py +23 -0
  31. scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
  32. scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
  33. scanoss/api/scanning/__init__.py +17 -17
  34. scanoss/api/scanning/v2/__init__.py +17 -17
  35. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +42 -13
  36. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +86 -7
  37. scanoss/api/semgrep/__init__.py +17 -17
  38. scanoss/api/semgrep/v2/__init__.py +17 -17
  39. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
  40. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
  41. scanoss/api/vulnerabilities/__init__.py +17 -17
  42. scanoss/api/vulnerabilities/v2/__init__.py +17 -17
  43. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
  44. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
  45. scanoss/cli.py +2359 -370
  46. scanoss/components.py +187 -94
  47. scanoss/constants.py +22 -0
  48. scanoss/cryptography.py +308 -0
  49. scanoss/csvoutput.py +91 -58
  50. scanoss/cyclonedx.py +221 -63
  51. scanoss/data/build_date.txt +1 -1
  52. scanoss/data/osadl-copyleft.json +133 -0
  53. scanoss/data/scanoss-settings-schema.json +254 -0
  54. scanoss/delta.py +197 -0
  55. scanoss/export/__init__.py +23 -0
  56. scanoss/export/dependency_track.py +227 -0
  57. scanoss/file_filters.py +582 -0
  58. scanoss/filecount.py +75 -69
  59. scanoss/gitlabqualityreport.py +214 -0
  60. scanoss/header_filter.py +563 -0
  61. scanoss/inspection/__init__.py +23 -0
  62. scanoss/inspection/policy_check/__init__.py +0 -0
  63. scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
  64. scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
  65. scanoss/inspection/policy_check/policy_check.py +222 -0
  66. scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
  67. scanoss/inspection/policy_check/scanoss/copyleft.py +243 -0
  68. scanoss/inspection/policy_check/scanoss/undeclared_component.py +309 -0
  69. scanoss/inspection/summary/__init__.py +0 -0
  70. scanoss/inspection/summary/component_summary.py +170 -0
  71. scanoss/inspection/summary/license_summary.py +191 -0
  72. scanoss/inspection/summary/match_summary.py +341 -0
  73. scanoss/inspection/utils/file_utils.py +44 -0
  74. scanoss/inspection/utils/license_utils.py +123 -0
  75. scanoss/inspection/utils/markdown_utils.py +63 -0
  76. scanoss/inspection/utils/scan_result_processor.py +417 -0
  77. scanoss/osadl.py +125 -0
  78. scanoss/results.py +275 -0
  79. scanoss/scancodedeps.py +87 -38
  80. scanoss/scanner.py +431 -539
  81. scanoss/scanners/__init__.py +23 -0
  82. scanoss/scanners/container_scanner.py +476 -0
  83. scanoss/scanners/folder_hasher.py +358 -0
  84. scanoss/scanners/scanner_config.py +73 -0
  85. scanoss/scanners/scanner_hfh.py +252 -0
  86. scanoss/scanoss_settings.py +337 -0
  87. scanoss/scanossapi.py +140 -101
  88. scanoss/scanossbase.py +59 -22
  89. scanoss/scanossgrpc.py +799 -251
  90. scanoss/scanpostprocessor.py +294 -0
  91. scanoss/scantype.py +22 -21
  92. scanoss/services/dependency_track_service.py +132 -0
  93. scanoss/spdxlite.py +532 -174
  94. scanoss/threadeddependencies.py +148 -47
  95. scanoss/threadedscanning.py +53 -37
  96. scanoss/utils/__init__.py +23 -0
  97. scanoss/utils/abstract_presenter.py +103 -0
  98. scanoss/utils/crc64.py +96 -0
  99. scanoss/utils/file.py +84 -0
  100. scanoss/utils/scanoss_scan_results_utils.py +41 -0
  101. scanoss/utils/simhash.py +198 -0
  102. scanoss/winnowing.py +241 -63
  103. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/METADATA +18 -9
  104. scanoss-1.43.1.dist-info/RECORD +110 -0
  105. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/WHEEL +1 -1
  106. scanoss-1.12.2.dist-info/RECORD +0 -58
  107. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
  108. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info/licenses}/LICENSE +0 -0
  109. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,23 @@
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
+ """
@@ -0,0 +1,476 @@
1
+ """
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
+ """
24
+
25
+ import json
26
+ import os
27
+ import subprocess
28
+ from dataclasses import dataclass
29
+ from typing import Dict, List, Optional, TypedDict
30
+
31
+ from scanoss.constants import DEFAULT_RETRY, DEFAULT_TIMEOUT
32
+ from scanoss.csvoutput import CsvOutput
33
+ from scanoss.cyclonedx import CycloneDx
34
+ from scanoss.scanossbase import ScanossBase
35
+ from scanoss.scanossgrpc import ScanossGrpc
36
+ from scanoss.spdxlite import SpdxLite
37
+ from scanoss.utils.abstract_presenter import AbstractPresenter
38
+
39
+ DEFAULT_SYFT_TIMEOUT = 600
40
+ DEFAULT_SYFT_COMMAND = 'syft'
41
+
42
+
43
+ @dataclass
44
+ class ContainerScannerConfig:
45
+ debug: bool = False
46
+ trace: bool = False
47
+ quiet: bool = False
48
+ retry: int = DEFAULT_RETRY
49
+ timeout: int = DEFAULT_TIMEOUT
50
+ output: Optional[str] = None
51
+ format: Optional[str] = None
52
+ apiurl: Optional[str] = None
53
+ ignore_cert_errors: bool = False
54
+ key: Optional[str] = None
55
+ proxy: Optional[str] = None
56
+ pac: Optional[str] = None
57
+ grpc_proxy: Optional[str] = None
58
+ ca_cert: Optional[str] = None
59
+ syft_command: str = DEFAULT_SYFT_COMMAND
60
+ syft_timeout: int = DEFAULT_SYFT_TIMEOUT
61
+ only_interim_results: bool = False
62
+
63
+
64
+ def create_container_scanner_config_from_args(args) -> ContainerScannerConfig:
65
+ return ContainerScannerConfig(
66
+ debug=args.debug if 'debug' in args else False,
67
+ trace=args.trace if 'trace' in args else False,
68
+ quiet=args.quiet if 'quiet' in args else False,
69
+ retry=args.retry if 'retry' in args else DEFAULT_RETRY,
70
+ timeout=args.timeout if 'timeout' in args else DEFAULT_TIMEOUT,
71
+ output=args.output if 'output' in args else None,
72
+ format=args.format if 'format' in args else None,
73
+ apiurl=args.api2url if 'api2url' in args else None,
74
+ proxy=args.proxy if 'proxy' in args else None,
75
+ pac=args.pac if 'pac' in args else None,
76
+ grpc_proxy=args.grpc_proxy if 'grpc_proxy' in args else None,
77
+ ca_cert=args.ca_cert if 'ca_cert' in args else None,
78
+ ignore_cert_errors=args.ignore_cert_errors if 'ignore_cert_errors' in args else False,
79
+ key=args.key if 'key' in args else None,
80
+ syft_command=args.syft_command if 'syft_command' in args else DEFAULT_SYFT_COMMAND,
81
+ syft_timeout=args.syft_timeout if 'syft_timeout' in args else DEFAULT_SYFT_TIMEOUT,
82
+ )
83
+
84
+
85
+ class LicenseItem(TypedDict):
86
+ value: str
87
+ spdxExpression: str
88
+ type: str
89
+
90
+ @classmethod
91
+ def from_dict(cls, data: dict):
92
+ return cls(**data)
93
+
94
+
95
+ class SyftArtifactItem(TypedDict):
96
+ name: str
97
+ version: str
98
+ type: str
99
+ purl: str
100
+ licenses: List[LicenseItem]
101
+
102
+ @classmethod
103
+ def from_dict(cls, data: dict):
104
+ return cls(
105
+ name=data['name'],
106
+ version=data['version'],
107
+ type=data['type'],
108
+ purl=data['purl'],
109
+ licenses=[LicenseItem.from_dict(lic) for lic in data['licenses']],
110
+ )
111
+
112
+
113
+ class SyftScanResult(TypedDict):
114
+ artifacts: List[SyftArtifactItem]
115
+
116
+ @classmethod
117
+ def from_dict(cls, data: dict):
118
+ return cls(artifacts=[SyftArtifactItem.from_dict(a) for a in data['artifacts']])
119
+
120
+
121
+ class PurlItem(TypedDict):
122
+ purl: str
123
+ requirement: Optional[str]
124
+
125
+
126
+ class ContainerScanResultFileItem(TypedDict):
127
+ file: str
128
+ purls: List[PurlItem]
129
+
130
+
131
+ class ContainerScanResult(TypedDict):
132
+ files: List[ContainerScanResultFileItem]
133
+
134
+
135
+ class DependencyLicenseItem(TypedDict):
136
+ value: str
137
+ spdxExpression: str
138
+ type: str
139
+
140
+
141
+ class DependencyItem(TypedDict):
142
+ purl: str
143
+ licenses: List[DependencyLicenseItem]
144
+
145
+
146
+ class DecoratedContainerScanResultFileItem(TypedDict):
147
+ file: str
148
+ id: str
149
+ status: str
150
+ dependencies: List[DependencyItem]
151
+
152
+
153
+ class DecoratedContainerScanResult(TypedDict):
154
+ files: List[ContainerScanResultFileItem]
155
+ status: Dict[str, str]
156
+
157
+
158
+ class SyftScanError(Exception):
159
+ """Base exception for Syft scan errors"""
160
+
161
+ pass
162
+
163
+
164
+ class SyftExecutionError(SyftScanError):
165
+ """Raised when Syft returns a non-zero exit code"""
166
+
167
+ pass
168
+
169
+
170
+ class SyftJsonError(SyftScanError):
171
+ """Raised when Syft output cannot be parsed as JSON"""
172
+
173
+ pass
174
+
175
+
176
+ class SyftTimeoutError(SyftScanError):
177
+ """Raised when a Syft scan times out"""
178
+
179
+ pass
180
+
181
+
182
+ class SCANOSSDependencyScanError(Exception):
183
+ """Base exception for SCANOSS dependency scan errors"""
184
+
185
+ pass
186
+
187
+
188
+ class DecorateScanResultsError(SCANOSSDependencyScanError):
189
+ """Raised when there is an issue decorating scan results with dependencies"""
190
+
191
+ pass
192
+
193
+
194
+ class ContainerScanner:
195
+ """SCANOSS container scanning class.
196
+
197
+ This class provides functionality to scan containers using Syft and process
198
+ the results into SCANOSS dependency format.
199
+ """
200
+
201
+ def __init__(
202
+ self,
203
+ config: ContainerScannerConfig,
204
+ what_to_scan: str,
205
+ ):
206
+ """Initialize ContainerScanner class.
207
+
208
+ Args:
209
+ config: ContainerScannerConfig object containing configuration settings.
210
+ """
211
+ self.base = ScanossBase(
212
+ debug=config.debug,
213
+ trace=config.trace,
214
+ quiet=config.quiet,
215
+ )
216
+ self.presenter = ContainerScannerPresenter(
217
+ self,
218
+ debug=config.debug,
219
+ trace=config.trace,
220
+ quiet=config.quiet,
221
+ output_file=config.output,
222
+ output_format=config.format,
223
+ )
224
+ self.grpc_api = ScanossGrpc(
225
+ debug=config.debug,
226
+ quiet=config.quiet,
227
+ trace=config.trace,
228
+ url=config.apiurl,
229
+ api_key=config.key,
230
+ ca_cert=config.ca_cert,
231
+ proxy=config.proxy,
232
+ pac=config.pac,
233
+ grpc_proxy=config.grpc_proxy,
234
+ )
235
+ self.what_to_scan: str = what_to_scan
236
+ self.syft_command: str = config.syft_command
237
+ self.syft_timeout: int = config.syft_timeout
238
+ self.only_interim_results: bool = config.only_interim_results
239
+ self.syft_output: Optional[SyftScanResult] = None
240
+ self.normalized_syft_output: Optional[ContainerScanResult] = None
241
+ self.decorated_scan_results: Optional[DecoratedContainerScanResult] = None
242
+
243
+ def decorate_scan_results_with_dependencies(self) -> None:
244
+ """
245
+ Decorate the scan results with dependencies.
246
+ """
247
+ try:
248
+ decorated_scan_results = self.grpc_api.get_dependencies(self.normalized_syft_output)
249
+ self.decorated_scan_results = decorated_scan_results
250
+ return decorated_scan_results
251
+ except Exception as e:
252
+ error_msg = f'Issue decorating scan results with dependencies: {e}'
253
+ self.base.print_stderr(f'ERROR: {error_msg}')
254
+ raise DecorateScanResultsError(error_msg) from e
255
+
256
+ def scan(
257
+ self,
258
+ ) -> ContainerScanResult:
259
+ """Run a syft scan of the specified target.
260
+
261
+ Returns:
262
+ ContainerScanResult: The container scan results.
263
+
264
+ Raises:
265
+ SyftScanError: For other scan-related errors
266
+ """
267
+ try:
268
+ self.syft_output = self._execute_syft_scan()
269
+ self.normalized_syft_output = self._normalize_syft_output()
270
+ return self.normalized_syft_output
271
+ except Exception as e:
272
+ if isinstance(e, SyftScanError):
273
+ raise
274
+ error_msg = f'Issue running syft scan on {self.what_to_scan}: {e}'
275
+ self.base.print_stderr(f'ERROR: {error_msg}')
276
+ raise SyftScanError(error_msg) from e
277
+
278
+ def _execute_syft_scan(self) -> SyftScanResult:
279
+ """
280
+ Execute a Syft scan of the specified target.
281
+
282
+ Returns:
283
+ SyftScanResult: The result of the Syft scan.
284
+
285
+ Raises:
286
+ SyftScanError: If the Syft scan fails.
287
+ SyftJsonError: If the Syft scan output cannot be parsed as JSON.
288
+ SyftTimeoutError: If the Syft scan times out.
289
+ SyftExecutionError: If the Syft scan execution fails.
290
+ """
291
+ try:
292
+ self.base.print_trace(f'About to execute {self.syft_command} scan {self.what_to_scan} -q -o json')
293
+ self.base.print_msg('Scanning container...')
294
+ result = subprocess.run(
295
+ [self.syft_command, 'scan', self.what_to_scan, '-q', '-o', 'json'],
296
+ cwd=os.getcwd(),
297
+ stdout=subprocess.PIPE,
298
+ stderr=subprocess.STDOUT,
299
+ text=True,
300
+ timeout=self.syft_timeout,
301
+ check=False,
302
+ )
303
+ self.base.print_trace(f'Subprocess return: {result}')
304
+
305
+ if result.returncode:
306
+ error_msg = (
307
+ f'Syft scan of {self.what_to_scan} failed with exit code {result.returncode}:\n{result.stdout}'
308
+ )
309
+ self.base.print_stderr(f'ERROR: {error_msg}')
310
+ raise SyftExecutionError(error_msg)
311
+
312
+ try:
313
+ json_data = json.loads(result.stdout)
314
+ return SyftScanResult.from_dict(json_data)
315
+ except json.JSONDecodeError as e:
316
+ error_msg = f'Failed to parse JSON output from syft: {e}\n{result.stdout}'
317
+ self.base.print_stderr(f'ERROR: {error_msg}')
318
+ raise SyftJsonError(error_msg) from e
319
+
320
+ except subprocess.TimeoutExpired as e:
321
+ error_msg = f'Timed out attempting to run syft scan on {self.what_to_scan}: {e}'
322
+ self.base.print_stderr(f'ERROR: {error_msg}')
323
+ raise SyftTimeoutError(error_msg) from e
324
+
325
+ except Exception as e:
326
+ if isinstance(e, SyftScanError):
327
+ raise
328
+ error_msg = f'Issue running syft scan on {self.what_to_scan}: {e}'
329
+ self.base.print_stderr(f'ERROR: {error_msg}')
330
+ raise SyftScanError(error_msg) from e
331
+
332
+ def _get_dependencies(self) -> None:
333
+ """
334
+ Run a dependency scan of the specified target.
335
+ """
336
+ try:
337
+ if not self.normalized_syft_output:
338
+ error_msg = 'Syft scan output is not available'
339
+ self.base.print_stderr(error_msg)
340
+ raise ValueError(error_msg)
341
+ if not self.grpc_api.get_dependencies(self.normalized_syft_output):
342
+ error_msg = 'Failed to get dependencies'
343
+ self.base.print_stderr(error_msg)
344
+ raise SCANOSSDependencyScanError(error_msg)
345
+ except Exception as e:
346
+ error_msg = f'Failed to run dependency scan: {e}'
347
+ self.base.print_stderr(error_msg)
348
+ raise SCANOSSDependencyScanError(error_msg)
349
+
350
+ def _normalize_syft_output(self) -> ContainerScanResult:
351
+ """
352
+ Normalize the Syft output data into the same format we use in dependency scanning
353
+
354
+ Returns:
355
+ ContainerScanResult: The normalized output
356
+ """
357
+ normalized_output = ContainerScanResult()
358
+
359
+ # This is a workaround because we don't have file paths as in dependency scanning, we use the container name
360
+ file_name = self.what_to_scan
361
+ artifacts = self.syft_output['artifacts']
362
+
363
+ unique_purls = set()
364
+ unique_purl_items = []
365
+
366
+ for artifact in artifacts:
367
+ purl = artifact['purl']
368
+ if purl not in unique_purls:
369
+ unique_purls.add(purl)
370
+ unique_purl_items.append(PurlItem(purl=purl))
371
+
372
+ normalized_output['files'] = [
373
+ {
374
+ 'file': file_name,
375
+ 'purls': unique_purl_items,
376
+ }
377
+ ]
378
+
379
+ return normalized_output
380
+
381
+ def present(self, output_format: str = None, output_file: str = None):
382
+ """Present the results in the selected format"""
383
+ self.presenter.present(output_format=output_format, output_file=output_file)
384
+
385
+
386
+ class ContainerScannerPresenter(AbstractPresenter):
387
+ """
388
+ ContainerScannerPresenter presenter class
389
+ Handles the presentation of the container scan results
390
+ """
391
+
392
+ def __init__(self, scanner: ContainerScanner, **kwargs):
393
+ super().__init__(**kwargs)
394
+ self.scanner = scanner
395
+ self.AVAILABLE_OUTPUT_FORMATS = ['plain', 'cyclonedx', 'spdxlite', 'csv', 'raw']
396
+
397
+ def _convert_raw_to_scan_output(self) -> dict:
398
+ """
399
+ Convert the raw output from dependency scanning API to our scan output format
400
+
401
+ Returns:
402
+ dict: The converted output
403
+ """
404
+ formatted_output = {}
405
+ if (
406
+ self.scanner.decorated_scan_results
407
+ and 'files' in self.scanner.decorated_scan_results
408
+ and self.scanner.decorated_scan_results['files']
409
+ and isinstance(self.scanner.decorated_scan_results['files'], list)
410
+ ):
411
+ file_item = self.scanner.decorated_scan_results['files'][0]
412
+ if file_item and isinstance(file_item, dict) and 'file' in file_item:
413
+ formatted_output[file_item['file']] = [file_item]
414
+
415
+ return formatted_output
416
+
417
+ def _format_plain_output(self) -> str:
418
+ """
419
+ Format the scan output data into a plain text string
420
+ """
421
+ return json.dumps(self._convert_raw_to_scan_output(), indent=2)
422
+
423
+ def _format_raw_output(self) -> str:
424
+ """
425
+ Format the scan output data into the raw output from dependency scanning API
426
+ """
427
+ if self.scanner.only_interim_results:
428
+ return json.dumps(self.scanner.normalized_syft_output, indent=2)
429
+ return json.dumps(self.scanner.decorated_scan_results, indent=2)
430
+
431
+ def _format_cyclonedx_output(self) -> str:
432
+ """
433
+ Format the scan output data into a CycloneDX object
434
+ """
435
+ cdx = CycloneDx(self.base.debug, self.output_file)
436
+ scan_results = {}
437
+ for f in self.scanner.decorated_scan_results['files']:
438
+ scan_results[f['file']] = [f]
439
+ success, cdx_output = cdx.produce_from_json(scan_results)
440
+ if not success:
441
+ error_msg = 'Failed to produce CycloneDX output'
442
+ self.base.print_stderr(error_msg)
443
+ return None
444
+ return json.dumps(cdx_output, indent=2)
445
+
446
+ def _format_spdxlite_output(self) -> str:
447
+ """
448
+ Format the scan output data into a SPDXLite object
449
+ """
450
+ spdxlite = SpdxLite(self.base.debug, self.output_file)
451
+ scan_results = {}
452
+ for f in self.scanner.decorated_scan_results['files']:
453
+ scan_results[f['file']] = [f]
454
+ if not spdxlite.produce_from_json(scan_results, self.output_file):
455
+ error_msg = 'Failed to produce SPDXLite output'
456
+ self.base.print_stderr(error_msg)
457
+ raise ValueError(error_msg)
458
+
459
+ def _format_csv_output(self) -> str:
460
+ """
461
+ Format the scan output data into a CSV object
462
+ """
463
+ csv = CsvOutput(self.base.debug, self.output_file)
464
+ scan_results = {}
465
+ for f in self.scanner.decorated_scan_results['files']:
466
+ scan_results[f['file']] = [f]
467
+ if not csv.produce_from_json(scan_results, self.output_file):
468
+ error_msg = 'Failed to produce CSV output'
469
+ self.base.print_stderr(error_msg)
470
+ raise ValueError(error_msg)
471
+
472
+ def _format_json_output(self) -> str:
473
+ """
474
+ Format the scan output data into a JSON object
475
+ """
476
+ pass