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
scanoss/cli.py
CHANGED
|
@@ -1,42 +1,96 @@
|
|
|
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) 2021, 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
|
import argparse
|
|
25
26
|
import os
|
|
26
27
|
import sys
|
|
28
|
+
import traceback
|
|
29
|
+
from dataclasses import asdict
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import List
|
|
27
32
|
|
|
28
33
|
import pypac
|
|
29
34
|
|
|
30
|
-
from .
|
|
35
|
+
from scanoss.cryptography import Cryptography, create_cryptography_config_from_args
|
|
36
|
+
from scanoss.delta import Delta
|
|
37
|
+
from scanoss.export.dependency_track import DependencyTrackExporter
|
|
38
|
+
from scanoss.scanners.container_scanner import (
|
|
39
|
+
DEFAULT_SYFT_COMMAND,
|
|
40
|
+
DEFAULT_SYFT_TIMEOUT,
|
|
41
|
+
ContainerScanner,
|
|
42
|
+
create_container_scanner_config_from_args,
|
|
43
|
+
)
|
|
44
|
+
from scanoss.scanners.folder_hasher import (
|
|
45
|
+
FolderHasher,
|
|
46
|
+
create_folder_hasher_config_from_args,
|
|
47
|
+
)
|
|
48
|
+
from scanoss.scanossgrpc import (
|
|
49
|
+
ScanossGrpc,
|
|
50
|
+
ScanossGrpcError,
|
|
51
|
+
create_grpc_config_from_args,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
from . import __version__
|
|
55
|
+
from .components import Components
|
|
56
|
+
from .constants import (
|
|
57
|
+
DEFAULT_API_TIMEOUT,
|
|
58
|
+
DEFAULT_COPYLEFT_LICENSE_SOURCES,
|
|
59
|
+
DEFAULT_HFH_DEPTH,
|
|
60
|
+
DEFAULT_HFH_MIN_ACCEPTED_SCORE,
|
|
61
|
+
DEFAULT_HFH_RANK_THRESHOLD,
|
|
62
|
+
DEFAULT_HFH_RECURSIVE_THRESHOLD,
|
|
63
|
+
DEFAULT_POST_SIZE,
|
|
64
|
+
DEFAULT_RETRY,
|
|
65
|
+
DEFAULT_TIMEOUT,
|
|
66
|
+
MIN_TIMEOUT,
|
|
67
|
+
PYTHON_MAJOR_VERSION,
|
|
68
|
+
VALID_LICENSE_SOURCES,
|
|
69
|
+
)
|
|
70
|
+
from .csvoutput import CsvOutput
|
|
71
|
+
from .cyclonedx import CycloneDx
|
|
72
|
+
from .filecount import FileCount
|
|
73
|
+
from .gitlabqualityreport import GitLabQualityReport
|
|
74
|
+
from .inspection.policy_check.dependency_track.project_violation import (
|
|
75
|
+
DependencyTrackProjectViolationPolicyCheck,
|
|
76
|
+
)
|
|
77
|
+
from .inspection.policy_check.scanoss.copyleft import Copyleft
|
|
78
|
+
from .inspection.policy_check.scanoss.undeclared_component import UndeclaredComponent
|
|
79
|
+
from .inspection.summary.component_summary import ComponentSummary
|
|
80
|
+
from .inspection.summary.license_summary import LicenseSummary
|
|
81
|
+
from .inspection.summary.match_summary import MatchSummary
|
|
82
|
+
from .results import Results
|
|
31
83
|
from .scancodedeps import ScancodeDeps
|
|
84
|
+
from .scanner import FAST_WINNOWING, Scanner
|
|
85
|
+
from .scanners.scanner_config import create_scanner_config_from_args
|
|
86
|
+
from .scanners.scanner_hfh import ScannerHFH
|
|
87
|
+
from .scanoss_settings import ScanossSettings, ScanossSettingsError
|
|
32
88
|
from .scantype import ScanType
|
|
33
|
-
from .filecount import FileCount
|
|
34
|
-
from .cyclonedx import CycloneDx
|
|
35
89
|
from .spdxlite import SpdxLite
|
|
36
|
-
from .
|
|
37
|
-
from .
|
|
38
|
-
|
|
39
|
-
|
|
90
|
+
from .threadeddependencies import SCOPE
|
|
91
|
+
from .utils.file import validate_json_file
|
|
92
|
+
|
|
93
|
+
HEADER_PARTS_COUNT = 2
|
|
40
94
|
|
|
41
95
|
|
|
42
96
|
def print_stderr(*args, **kwargs):
|
|
@@ -46,134 +100,267 @@ def print_stderr(*args, **kwargs):
|
|
|
46
100
|
print(*args, file=sys.stderr, **kwargs)
|
|
47
101
|
|
|
48
102
|
|
|
49
|
-
def setup_args() -> None:
|
|
103
|
+
def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
50
104
|
"""
|
|
51
105
|
Setup all the command line arguments for processing
|
|
52
106
|
"""
|
|
53
|
-
parser = argparse.ArgumentParser(
|
|
107
|
+
parser = argparse.ArgumentParser(
|
|
108
|
+
description=f'SCANOSS Python CLI. Ver: {__version__}, License: MIT, Fast Winnowing: {FAST_WINNOWING}'
|
|
109
|
+
)
|
|
54
110
|
parser.add_argument('--version', '-v', action='store_true', help='Display version details')
|
|
55
111
|
|
|
56
|
-
subparsers = parser.add_subparsers(
|
|
57
|
-
|
|
112
|
+
subparsers = parser.add_subparsers(
|
|
113
|
+
title='Sub Commands', dest='subparser', description='valid subcommands', help='sub-command help'
|
|
114
|
+
)
|
|
58
115
|
# Sub-command: version
|
|
59
|
-
p_ver = subparsers.add_parser(
|
|
60
|
-
|
|
116
|
+
p_ver = subparsers.add_parser(
|
|
117
|
+
'version', aliases=['ver'], description=f'Version of SCANOSS CLI: {__version__}', help='SCANOSS version'
|
|
118
|
+
)
|
|
61
119
|
p_ver.set_defaults(func=ver)
|
|
62
120
|
|
|
63
121
|
# Sub-command: scan
|
|
64
|
-
p_scan = subparsers.add_parser(
|
|
65
|
-
|
|
66
|
-
|
|
122
|
+
p_scan = subparsers.add_parser(
|
|
123
|
+
'scan',
|
|
124
|
+
aliases=['sc'],
|
|
125
|
+
description=f'Analyse/scan the given source base: {__version__}',
|
|
126
|
+
help='Scan source code',
|
|
127
|
+
)
|
|
67
128
|
p_scan.set_defaults(func=scan)
|
|
68
129
|
p_scan.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
|
|
69
|
-
p_scan.add_argument('--wfp', '-w',
|
|
70
|
-
|
|
71
|
-
p_scan.add_argument(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
130
|
+
p_scan.add_argument('--wfp', '-w', type=str, help='Scan a WFP File instead of a folder (optional)')
|
|
131
|
+
p_scan.add_argument('--dep', '-p', type=str, help='Use a dependency file instead of a folder (optional)')
|
|
132
|
+
p_scan.add_argument(
|
|
133
|
+
'--stdin', '-s', metavar='STDIN-FILENAME', type=str, help='Scan the file contents supplied via STDIN (optional)'
|
|
134
|
+
)
|
|
135
|
+
p_scan.add_argument('--files', '-e', type=str, nargs='*', help='List of files to scan.')
|
|
75
136
|
p_scan.add_argument('--identify', '-i', type=str, help='Scan and identify components in SBOM file')
|
|
76
|
-
p_scan.add_argument('--ignore',
|
|
77
|
-
p_scan.add_argument(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
p_scan.add_argument(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
p_scan.add_argument(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
137
|
+
p_scan.add_argument('--ignore', '-n', type=str, help='Ignore components specified in the SBOM file')
|
|
138
|
+
p_scan.add_argument(
|
|
139
|
+
'--threads', '-T', type=int, default=5, help='Number of threads to use while scanning (optional - default 5)'
|
|
140
|
+
)
|
|
141
|
+
p_scan.add_argument(
|
|
142
|
+
'--flags',
|
|
143
|
+
'-F',
|
|
144
|
+
type=int,
|
|
145
|
+
help='Scanning engine flags (1: disable snippet matching, 2 enable snippet ids, '
|
|
146
|
+
'4: disable dependencies, 8: disable licenses, 16: disable copyrights,'
|
|
147
|
+
'32: disable vulnerabilities, 64: disable quality, 128: disable cryptography,'
|
|
148
|
+
'256: disable best match only, 512: hide identified files, '
|
|
149
|
+
'1024: enable download_url, 2048: enable GitHub full path, '
|
|
150
|
+
'4096: disable extended server stats)',
|
|
151
|
+
)
|
|
152
|
+
p_scan.add_argument(
|
|
153
|
+
'--post-size',
|
|
154
|
+
'-P',
|
|
155
|
+
type=int,
|
|
156
|
+
default=DEFAULT_POST_SIZE,
|
|
157
|
+
help='Number of kilobytes to limit the post to while scanning (optional - default 32)',
|
|
158
|
+
)
|
|
159
|
+
p_scan.add_argument(
|
|
160
|
+
'--timeout',
|
|
161
|
+
'-M',
|
|
162
|
+
type=int,
|
|
163
|
+
default=DEFAULT_TIMEOUT,
|
|
164
|
+
help='Timeout (in seconds) for API communication (optional - default 180)',
|
|
165
|
+
)
|
|
166
|
+
p_scan.add_argument(
|
|
167
|
+
'--retry',
|
|
168
|
+
'-R',
|
|
169
|
+
type=int,
|
|
170
|
+
default=DEFAULT_RETRY,
|
|
171
|
+
help='Retry limit for API communication (optional - default 5)',
|
|
172
|
+
)
|
|
96
173
|
p_scan.add_argument('--dependencies', '-D', action='store_true', help='Add Dependency scanning')
|
|
97
174
|
p_scan.add_argument('--dependencies-only', action='store_true', help='Run Dependency scanning only')
|
|
98
|
-
p_scan.add_argument(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
175
|
+
p_scan.add_argument(
|
|
176
|
+
'--sc-command', type=str, help='Scancode command and path if required (optional - default scancode).'
|
|
177
|
+
)
|
|
178
|
+
p_scan.add_argument(
|
|
179
|
+
'--sc-timeout',
|
|
180
|
+
type=int,
|
|
181
|
+
default=600,
|
|
182
|
+
help='Timeout (in seconds) for scancode to complete (optional - default 600)',
|
|
183
|
+
)
|
|
184
|
+
p_scan.add_argument(
|
|
185
|
+
'--dep-scope', '-ds', type=SCOPE, help='Filter dependencies by scope - default all (options: dev/prod)'
|
|
186
|
+
)
|
|
187
|
+
p_scan.add_argument('--dep-scope-inc', '-dsi', type=str, help='Include dependencies with declared scopes')
|
|
188
|
+
p_scan.add_argument('--dep-scope-exc', '-dse', type=str, help='Exclude dependencies with declared scopes')
|
|
189
|
+
p_scan.add_argument(
|
|
190
|
+
'--no-wfp-output', action='store_true',
|
|
191
|
+
help='DEPRECATED: Scans no longer generate scanner_output.wfp. Use "fingerprint -o" to create WFP files.'
|
|
192
|
+
)
|
|
102
193
|
|
|
103
194
|
# Sub-command: fingerprint
|
|
104
|
-
p_wfp = subparsers.add_parser(
|
|
105
|
-
|
|
106
|
-
|
|
195
|
+
p_wfp = subparsers.add_parser(
|
|
196
|
+
'fingerprint',
|
|
197
|
+
aliases=['fp', 'wfp'],
|
|
198
|
+
description=f'Fingerprint the given source base: {__version__}',
|
|
199
|
+
help='Fingerprint source code',
|
|
200
|
+
)
|
|
107
201
|
p_wfp.set_defaults(func=wfp)
|
|
108
|
-
p_wfp.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?',
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
202
|
+
p_wfp.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
|
|
203
|
+
p_wfp.add_argument(
|
|
204
|
+
'--stdin',
|
|
205
|
+
'-s',
|
|
206
|
+
metavar='STDIN-FILENAME',
|
|
207
|
+
type=str,
|
|
208
|
+
help='Fingerprint the file contents supplied via STDIN (optional)',
|
|
209
|
+
)
|
|
113
210
|
|
|
114
211
|
# Sub-command: dependency
|
|
115
|
-
p_dep = subparsers.add_parser(
|
|
116
|
-
|
|
117
|
-
|
|
212
|
+
p_dep = subparsers.add_parser(
|
|
213
|
+
'dependencies',
|
|
214
|
+
aliases=['dp', 'dep'],
|
|
215
|
+
description=f'Produce dependency file summary: {__version__}',
|
|
216
|
+
help='Scan source code for dependencies, but do not decorate them',
|
|
217
|
+
)
|
|
218
|
+
p_dep.add_argument('scan_loc', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
|
|
219
|
+
p_dep.add_argument(
|
|
220
|
+
'--container',
|
|
221
|
+
type=str,
|
|
222
|
+
help='Container image to scan. Supports yourrepo/yourimage:tag, Docker tar, '
|
|
223
|
+
'OCI tar, OCI directory, SIF Container, or generic filesystem directory.',
|
|
224
|
+
)
|
|
225
|
+
p_dep.add_argument(
|
|
226
|
+
'--sc-command', type=str, help='Scancode command and path if required (optional - default scancode).'
|
|
227
|
+
)
|
|
228
|
+
p_dep.add_argument(
|
|
229
|
+
'--sc-timeout',
|
|
230
|
+
type=int,
|
|
231
|
+
default=600,
|
|
232
|
+
help='Timeout (in seconds) for scancode to complete (optional - default 600)',
|
|
233
|
+
)
|
|
118
234
|
p_dep.set_defaults(func=dependency)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
235
|
+
|
|
236
|
+
# Container scan sub-command
|
|
237
|
+
p_cs = subparsers.add_parser(
|
|
238
|
+
'container-scan',
|
|
239
|
+
aliases=['cs'],
|
|
240
|
+
description=f'Analyse/scan the given container image: {__version__}',
|
|
241
|
+
help='Scan container image',
|
|
242
|
+
)
|
|
243
|
+
p_cs.add_argument(
|
|
244
|
+
'scan_loc',
|
|
245
|
+
metavar='IMAGE',
|
|
246
|
+
type=str,
|
|
247
|
+
nargs='?',
|
|
248
|
+
help=(
|
|
249
|
+
'Container image to scan. Supports yourrepo/yourimage:tag, Docker tar, '
|
|
250
|
+
'OCI tar, OCI directory, SIF Container, or generic filesystem directory.'
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
p_cs.add_argument(
|
|
254
|
+
'--retry',
|
|
255
|
+
'-R',
|
|
256
|
+
type=int,
|
|
257
|
+
default=DEFAULT_RETRY,
|
|
258
|
+
help='Retry limit for API communication (optional - default 5)',
|
|
259
|
+
)
|
|
260
|
+
p_cs.add_argument(
|
|
261
|
+
'--timeout',
|
|
262
|
+
'-M',
|
|
263
|
+
type=int,
|
|
264
|
+
default=DEFAULT_TIMEOUT,
|
|
265
|
+
help='Timeout (in seconds) for API communication (optional - default 180)',
|
|
266
|
+
)
|
|
267
|
+
p_cs.set_defaults(func=container_scan)
|
|
125
268
|
|
|
126
269
|
# Sub-command: file_count
|
|
127
|
-
p_fc = subparsers.add_parser(
|
|
128
|
-
|
|
129
|
-
|
|
270
|
+
p_fc = subparsers.add_parser(
|
|
271
|
+
'file_count',
|
|
272
|
+
aliases=['fc'],
|
|
273
|
+
description=f'Produce a file type count summary: {__version__}',
|
|
274
|
+
help='Search the source tree and produce a file type summary',
|
|
275
|
+
)
|
|
130
276
|
p_fc.set_defaults(func=file_count)
|
|
131
277
|
p_fc.add_argument('scan_dir', metavar='DIR', type=str, nargs='?', help='A folder to search')
|
|
132
|
-
p_fc.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
133
278
|
p_fc.add_argument('--all-hidden', action='store_true', help='Scan all hidden files/folders')
|
|
134
279
|
|
|
135
280
|
# Sub-command: convert
|
|
136
|
-
p_cnv = subparsers.add_parser(
|
|
137
|
-
|
|
138
|
-
|
|
281
|
+
p_cnv = subparsers.add_parser(
|
|
282
|
+
'convert',
|
|
283
|
+
aliases=['cv', 'cnv', 'cvrt'],
|
|
284
|
+
description=f'Convert results files between formats: {__version__}',
|
|
285
|
+
help='Convert file format',
|
|
286
|
+
)
|
|
139
287
|
p_cnv.set_defaults(func=convert)
|
|
140
288
|
p_cnv.add_argument('--input', '-i', type=str, required=True, help='Input file name')
|
|
141
|
-
p_cnv.add_argument(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
289
|
+
p_cnv.add_argument(
|
|
290
|
+
'--format',
|
|
291
|
+
'-f',
|
|
292
|
+
type=str,
|
|
293
|
+
choices=['cyclonedx', 'spdxlite', 'csv', 'glc-codequality'],
|
|
294
|
+
default='spdxlite',
|
|
295
|
+
help='Output format (optional - default: spdxlite)',
|
|
296
|
+
)
|
|
297
|
+
p_cnv.add_argument(
|
|
298
|
+
'--input-format', type=str, choices=['plain'], default='plain', help='Input format (optional - default: plain)'
|
|
299
|
+
)
|
|
146
300
|
|
|
147
301
|
# Sub-command: component
|
|
148
|
-
p_comp = subparsers.add_parser(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
302
|
+
p_comp = subparsers.add_parser(
|
|
303
|
+
'component',
|
|
304
|
+
aliases=['comp'],
|
|
305
|
+
description=f'SCANOSS Component commands: {__version__}',
|
|
306
|
+
help='Component support commands',
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
comp_sub = p_comp.add_subparsers(
|
|
310
|
+
title='Component Commands',
|
|
311
|
+
dest='subparsercmd',
|
|
312
|
+
description='component sub-commands',
|
|
313
|
+
help='component sub-commands',
|
|
314
|
+
)
|
|
160
315
|
|
|
161
316
|
# Component Sub-command: component vulns
|
|
162
|
-
c_vulns = comp_sub.add_parser(
|
|
163
|
-
|
|
164
|
-
|
|
317
|
+
c_vulns = comp_sub.add_parser(
|
|
318
|
+
'vulns',
|
|
319
|
+
aliases=['vulnerabilities', 'vu'],
|
|
320
|
+
description=f'Show Vulnerability details: {__version__}',
|
|
321
|
+
help='Retrieve vulnerabilities for the given components',
|
|
322
|
+
)
|
|
165
323
|
c_vulns.set_defaults(func=comp_vulns)
|
|
166
324
|
|
|
325
|
+
# Component Sub-command: component licenses
|
|
326
|
+
c_licenses = comp_sub.add_parser(
|
|
327
|
+
'licenses',
|
|
328
|
+
aliases=['lics'],
|
|
329
|
+
description=f'Show License details: {__version__}',
|
|
330
|
+
help='Retrieve licenses for the given components',
|
|
331
|
+
)
|
|
332
|
+
c_licenses.set_defaults(func=comp_licenses)
|
|
333
|
+
|
|
167
334
|
# Component Sub-command: component semgrep
|
|
168
|
-
c_semgrep = comp_sub.add_parser(
|
|
169
|
-
|
|
170
|
-
|
|
335
|
+
c_semgrep = comp_sub.add_parser(
|
|
336
|
+
'semgrep',
|
|
337
|
+
aliases=['sp'],
|
|
338
|
+
description=f'Show Semgrep findings: {__version__}',
|
|
339
|
+
help='Retrieve semgrep issues/findings for the given components',
|
|
340
|
+
)
|
|
171
341
|
c_semgrep.set_defaults(func=comp_semgrep)
|
|
172
342
|
|
|
343
|
+
# Component Sub-command: component provenance
|
|
344
|
+
c_provenance = comp_sub.add_parser(
|
|
345
|
+
'provenance',
|
|
346
|
+
aliases=['prov', 'prv'],
|
|
347
|
+
description=f'Show GEO Provenance findings: {__version__}',
|
|
348
|
+
help='Retrieve geoprovenance for the given components',
|
|
349
|
+
)
|
|
350
|
+
c_provenance.add_argument(
|
|
351
|
+
'--origin',
|
|
352
|
+
action='store_true',
|
|
353
|
+
help='Retrieve geoprovenance using contributors origin (default: declared origin)',
|
|
354
|
+
)
|
|
355
|
+
c_provenance.set_defaults(func=comp_provenance)
|
|
356
|
+
|
|
173
357
|
# Component Sub-command: component search
|
|
174
|
-
c_search = comp_sub.add_parser(
|
|
175
|
-
|
|
176
|
-
|
|
358
|
+
c_search = comp_sub.add_parser(
|
|
359
|
+
'search',
|
|
360
|
+
aliases=['sc'],
|
|
361
|
+
description=f'Search component details: {__version__}',
|
|
362
|
+
help='Search for a KB component',
|
|
363
|
+
)
|
|
177
364
|
c_search.add_argument('--input', '-i', type=str, help='Input file name')
|
|
178
365
|
c_search.add_argument('--search', '-s', type=str, help='Generic component search')
|
|
179
366
|
c_search.add_argument('--vendor', '-v', type=str, help='Generic component search')
|
|
@@ -184,127 +371,939 @@ def setup_args() -> None:
|
|
|
184
371
|
c_search.set_defaults(func=comp_search)
|
|
185
372
|
|
|
186
373
|
# Component Sub-command: component versions
|
|
187
|
-
c_versions = comp_sub.add_parser(
|
|
188
|
-
|
|
189
|
-
|
|
374
|
+
c_versions = comp_sub.add_parser(
|
|
375
|
+
'versions',
|
|
376
|
+
aliases=['vs'],
|
|
377
|
+
description=f'Get component version details: {__version__}',
|
|
378
|
+
help='Search for component versions',
|
|
379
|
+
)
|
|
190
380
|
c_versions.add_argument('--input', '-i', type=str, help='Input file name')
|
|
191
381
|
c_versions.add_argument('--purl', '-p', type=str, help='Generic component search')
|
|
192
382
|
c_versions.add_argument('--limit', '-l', type=int, help='Generic component search')
|
|
193
383
|
c_versions.set_defaults(func=comp_versions)
|
|
194
384
|
|
|
385
|
+
# Sub-command: crypto
|
|
386
|
+
p_crypto = subparsers.add_parser(
|
|
387
|
+
'crypto',
|
|
388
|
+
aliases=['cr'],
|
|
389
|
+
description=f'SCANOSS Crypto commands: {__version__}',
|
|
390
|
+
help='Crypto support commands',
|
|
391
|
+
)
|
|
392
|
+
crypto_sub = p_crypto.add_subparsers(
|
|
393
|
+
title='Crypto Commands',
|
|
394
|
+
dest='subparsercmd',
|
|
395
|
+
description='crypto sub-commands',
|
|
396
|
+
help='crypto sub-commands',
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# GetAlgorithms and GetAlgorithmsInRange gRPC APIs
|
|
400
|
+
p_crypto_algorithms = crypto_sub.add_parser(
|
|
401
|
+
'algorithms',
|
|
402
|
+
aliases=['alg'],
|
|
403
|
+
description=f'Show Cryptographic algorithms: {__version__}',
|
|
404
|
+
help='Retrieve cryptographic algorithms for the given components',
|
|
405
|
+
)
|
|
406
|
+
p_crypto_algorithms.add_argument(
|
|
407
|
+
'--with-range',
|
|
408
|
+
action='store_true',
|
|
409
|
+
help='Returns the list of versions in the specified range that contains cryptographic algorithms',
|
|
410
|
+
)
|
|
411
|
+
p_crypto_algorithms.set_defaults(func=crypto_algorithms)
|
|
412
|
+
|
|
413
|
+
# GetEncryptionHints and GetHintsInRange gRPC APIs
|
|
414
|
+
p_crypto_hints = crypto_sub.add_parser(
|
|
415
|
+
'hints',
|
|
416
|
+
description=f'Show Encryption hints: {__version__}',
|
|
417
|
+
help='Retrieve encryption hints for the given components',
|
|
418
|
+
)
|
|
419
|
+
p_crypto_hints.add_argument(
|
|
420
|
+
'--with-range',
|
|
421
|
+
action='store_true',
|
|
422
|
+
help='Returns the list of versions in the specified range that contains encryption hints',
|
|
423
|
+
)
|
|
424
|
+
p_crypto_hints.set_defaults(func=crypto_hints)
|
|
425
|
+
|
|
426
|
+
p_crypto_versions_in_range = crypto_sub.add_parser(
|
|
427
|
+
'versions-in-range',
|
|
428
|
+
aliases=['vr'],
|
|
429
|
+
description=f'Show versions in range: {__version__}',
|
|
430
|
+
help="Given a list of PURLS and version ranges, get a list of versions that do/don't contain crypto algorithms",
|
|
431
|
+
)
|
|
432
|
+
p_crypto_versions_in_range.set_defaults(func=crypto_versions_in_range)
|
|
433
|
+
|
|
195
434
|
# Common purl Component sub-command options
|
|
196
|
-
for p in [
|
|
197
|
-
|
|
435
|
+
for p in [
|
|
436
|
+
c_vulns,
|
|
437
|
+
c_semgrep,
|
|
438
|
+
c_provenance,
|
|
439
|
+
p_crypto_algorithms,
|
|
440
|
+
p_crypto_hints,
|
|
441
|
+
p_crypto_versions_in_range,
|
|
442
|
+
c_licenses,
|
|
443
|
+
]:
|
|
444
|
+
p.add_argument('--purl', '-p', type=str, nargs='*', help='Package URL - PURL to process.')
|
|
198
445
|
p.add_argument('--input', '-i', type=str, help='Input file name')
|
|
446
|
+
|
|
199
447
|
# Common Component sub-command options
|
|
200
|
-
for p in [
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
448
|
+
for p in [
|
|
449
|
+
c_vulns,
|
|
450
|
+
c_search,
|
|
451
|
+
c_versions,
|
|
452
|
+
c_semgrep,
|
|
453
|
+
c_provenance,
|
|
454
|
+
p_crypto_algorithms,
|
|
455
|
+
p_crypto_hints,
|
|
456
|
+
p_crypto_versions_in_range,
|
|
457
|
+
c_licenses,
|
|
458
|
+
]:
|
|
459
|
+
p.add_argument(
|
|
460
|
+
'--timeout',
|
|
461
|
+
'-M',
|
|
462
|
+
type=int,
|
|
463
|
+
default=DEFAULT_API_TIMEOUT,
|
|
464
|
+
help='Timeout (in seconds) for API communication (optional - default 600)',
|
|
465
|
+
)
|
|
204
466
|
|
|
205
467
|
# Sub-command: utils
|
|
206
|
-
p_util = subparsers.add_parser(
|
|
207
|
-
|
|
208
|
-
|
|
468
|
+
p_util = subparsers.add_parser(
|
|
469
|
+
'utils',
|
|
470
|
+
aliases=['ut'],
|
|
471
|
+
description=f'SCANOSS Utility commands: {__version__}',
|
|
472
|
+
help='General utility support commands',
|
|
473
|
+
)
|
|
209
474
|
|
|
210
|
-
utils_sub = p_util.add_subparsers(
|
|
211
|
-
|
|
475
|
+
utils_sub = p_util.add_subparsers(
|
|
476
|
+
title='Utils Commands', dest='subparsercmd', description='utils sub-commands', help='utils sub-commands'
|
|
477
|
+
)
|
|
212
478
|
|
|
213
479
|
# Utils Sub-command: utils fast
|
|
214
|
-
p_f_f = utils_sub.add_parser(
|
|
215
|
-
|
|
480
|
+
p_f_f = utils_sub.add_parser(
|
|
481
|
+
'fast', description=f'Is fast winnowing enabled: {__version__}', help='SCANOSS fast winnowing'
|
|
482
|
+
)
|
|
216
483
|
p_f_f.set_defaults(func=fast)
|
|
217
484
|
|
|
218
485
|
# Utils Sub-command: utils certloc
|
|
219
|
-
p_c_loc = utils_sub.add_parser(
|
|
220
|
-
|
|
221
|
-
|
|
486
|
+
p_c_loc = utils_sub.add_parser(
|
|
487
|
+
'certloc',
|
|
488
|
+
aliases=['cl'],
|
|
489
|
+
description=f'Show location of Python CA Certs: {__version__}',
|
|
490
|
+
help='Display the location of Python CA Certs',
|
|
491
|
+
)
|
|
222
492
|
p_c_loc.set_defaults(func=utils_certloc)
|
|
223
493
|
|
|
224
494
|
# Utils Sub-command: utils cert-download
|
|
225
|
-
p_c_dwnld = utils_sub.add_parser(
|
|
226
|
-
|
|
227
|
-
|
|
495
|
+
p_c_dwnld = utils_sub.add_parser(
|
|
496
|
+
'cert-download',
|
|
497
|
+
aliases=['cdl', 'cert-dl'],
|
|
498
|
+
description=f'Download Server SSL Cert: {__version__}',
|
|
499
|
+
help="Download the specified server's SSL PEM certificate",
|
|
500
|
+
)
|
|
228
501
|
p_c_dwnld.set_defaults(func=utils_cert_download)
|
|
229
502
|
p_c_dwnld.add_argument('--hostname', '-n', required=True, type=str, help='Server hostname to download cert from.')
|
|
230
|
-
p_c_dwnld.add_argument(
|
|
231
|
-
|
|
232
|
-
|
|
503
|
+
p_c_dwnld.add_argument(
|
|
504
|
+
'--port', '-p', required=False, type=int, default=443, help='Server port number (default: 443).'
|
|
505
|
+
)
|
|
233
506
|
|
|
234
507
|
# Utils Sub-command: utils pac-proxy
|
|
235
|
-
p_p_proxy = utils_sub.add_parser(
|
|
236
|
-
|
|
237
|
-
|
|
508
|
+
p_p_proxy = utils_sub.add_parser(
|
|
509
|
+
'pac-proxy',
|
|
510
|
+
aliases=['pac'],
|
|
511
|
+
description=f'Determine Proxy from PAC: {__version__}',
|
|
512
|
+
help='Use Proxy Auto-Config to determine proxy configuration',
|
|
513
|
+
)
|
|
238
514
|
p_p_proxy.set_defaults(func=utils_pac_proxy)
|
|
239
|
-
p_p_proxy.add_argument(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
515
|
+
p_p_proxy.add_argument(
|
|
516
|
+
'--pac',
|
|
517
|
+
required=False,
|
|
518
|
+
type=str,
|
|
519
|
+
default='auto',
|
|
520
|
+
help='Proxy auto configuration. Specify a file, http url or "auto" to try to discover it.',
|
|
521
|
+
)
|
|
522
|
+
p_p_proxy.add_argument(
|
|
523
|
+
'--url',
|
|
524
|
+
required=False,
|
|
525
|
+
type=str,
|
|
526
|
+
default='https://api.osskb.org',
|
|
527
|
+
help='URL to test (default: https://api.osskb.org).',
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
p_results = subparsers.add_parser(
|
|
531
|
+
'results',
|
|
532
|
+
aliases=['res'],
|
|
533
|
+
description=f'SCANOSS Results commands: {__version__}',
|
|
534
|
+
help='Process scan results',
|
|
535
|
+
)
|
|
536
|
+
p_results.add_argument(
|
|
537
|
+
'filepath',
|
|
538
|
+
metavar='FILEPATH',
|
|
539
|
+
type=str,
|
|
540
|
+
nargs='?',
|
|
541
|
+
help='Path to the file containing the results',
|
|
542
|
+
)
|
|
543
|
+
p_results.add_argument(
|
|
544
|
+
'--match-type',
|
|
545
|
+
'-mt',
|
|
546
|
+
help='Filter results by match type (comma-separated, e.g., file,snippet)',
|
|
547
|
+
)
|
|
548
|
+
p_results.add_argument(
|
|
549
|
+
'--status',
|
|
550
|
+
'-s',
|
|
551
|
+
help='Filter results by file status (comma-separated, e.g., pending, identified)',
|
|
552
|
+
)
|
|
553
|
+
p_results.add_argument(
|
|
554
|
+
'--has-pending',
|
|
555
|
+
action='store_true',
|
|
556
|
+
help='Filter results to only include files with pending status',
|
|
557
|
+
)
|
|
558
|
+
p_results.add_argument(
|
|
559
|
+
'--output',
|
|
560
|
+
'-o',
|
|
561
|
+
help='Output result file',
|
|
562
|
+
)
|
|
563
|
+
p_results.add_argument(
|
|
564
|
+
'--format',
|
|
565
|
+
'-f',
|
|
566
|
+
choices=['json', 'plain'],
|
|
567
|
+
help='Output format (default: plain)',
|
|
568
|
+
)
|
|
569
|
+
p_results.set_defaults(func=results)
|
|
570
|
+
|
|
571
|
+
# =========================================================================
|
|
572
|
+
# INSPECT SUBCOMMAND - Analysis and validation of scan results
|
|
573
|
+
# =========================================================================
|
|
574
|
+
|
|
575
|
+
# Main inspect parser - provides tools for analyzing scan results
|
|
576
|
+
p_inspect = subparsers.add_parser(
|
|
577
|
+
'inspect',
|
|
578
|
+
aliases=['insp', 'ins'],
|
|
579
|
+
description=f'Inspect and analyse scan results: {__version__}',
|
|
580
|
+
help='Inspect and analyse scan results',
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# Inspect sub-commands parser
|
|
584
|
+
p_inspect_sub = p_inspect.add_subparsers(
|
|
585
|
+
title='Inspect Commands',
|
|
586
|
+
dest='subparsercmd',
|
|
587
|
+
description='Available inspection sub-commands',
|
|
588
|
+
help='Choose an inspection type',
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# -------------------------------------------------------------------------
|
|
592
|
+
# RAW RESULTS INSPECTION - Analyse raw scan output
|
|
593
|
+
# -------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
# Raw results parser - handles inspection of unprocessed scan results
|
|
596
|
+
p_inspect_raw = p_inspect_sub.add_parser(
|
|
597
|
+
'raw',
|
|
598
|
+
description='Inspect and analyse SCANOSS raw scan results',
|
|
599
|
+
help='Analyse raw scan results for various compliance issues',
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# Raw results sub-commands parser
|
|
603
|
+
p_inspect_raw_sub = p_inspect_raw.add_subparsers(
|
|
604
|
+
title='Raw Results Inspection Commands',
|
|
605
|
+
dest='subparser_subcmd',
|
|
606
|
+
description='Tools for analyzing raw scan results',
|
|
607
|
+
help='Choose a raw results analysis type',
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# Copyleft license inspection - identifies copyleft license violations
|
|
611
|
+
p_inspect_raw_copyleft = p_inspect_raw_sub.add_parser(
|
|
612
|
+
'copyleft',
|
|
613
|
+
aliases=['cp'],
|
|
614
|
+
description='Identify components with copyleft licenses that may require compliance action',
|
|
615
|
+
help='Find copyleft license violations',
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
# License summary inspection - provides overview of all detected licenses
|
|
619
|
+
p_inspect_raw_license_summary = p_inspect_raw_sub.add_parser(
|
|
620
|
+
'license-summary',
|
|
621
|
+
aliases=['lic-summary', 'licsum'],
|
|
622
|
+
description='Generate comprehensive summary of all licenses found in scan results',
|
|
623
|
+
help='Generate license summary report',
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
# Component summary inspection - provides overview of all detected components
|
|
627
|
+
p_inspect_raw_component_summary = p_inspect_raw_sub.add_parser(
|
|
628
|
+
'component-summary',
|
|
629
|
+
aliases=['comp-summary', 'compsum'],
|
|
630
|
+
description='Generate comprehensive summary of all components found in scan results',
|
|
631
|
+
help='Generate component summary report',
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# Undeclared components inspection - finds components not declared in SBOM
|
|
635
|
+
p_inspect_raw_undeclared = p_inspect_raw_sub.add_parser(
|
|
636
|
+
'undeclared',
|
|
637
|
+
aliases=['un'],
|
|
638
|
+
description='Identify components present in code but not declared in SBOM files',
|
|
639
|
+
help='Find undeclared components',
|
|
640
|
+
)
|
|
641
|
+
# SBOM format option for undeclared components inspection
|
|
642
|
+
p_inspect_raw_undeclared.add_argument(
|
|
643
|
+
'--sbom-format',
|
|
644
|
+
required=False,
|
|
645
|
+
choices=['legacy', 'settings'],
|
|
646
|
+
default='settings',
|
|
647
|
+
help='SBOM format type for comparison: legacy or settings (default)',
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
# -------------------------------------------------------------------------
|
|
651
|
+
# BACKWARD COMPATIBILITY - Support old inspect command format
|
|
652
|
+
# -------------------------------------------------------------------------
|
|
653
|
+
|
|
654
|
+
# Legacy copyleft inspection - backward compatibility for 'scanoss-py inspect copyleft'
|
|
655
|
+
p_inspect_legacy_copyleft = p_inspect_sub.add_parser(
|
|
656
|
+
'copyleft',
|
|
657
|
+
aliases=['cp'],
|
|
658
|
+
description='Identify components with copyleft licenses that may require compliance action',
|
|
659
|
+
help='Find copyleft license violations (legacy format)',
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
# Legacy undeclared components inspection - backward compatibility for 'scanoss-py inspect undeclared'
|
|
663
|
+
p_inspect_legacy_undeclared = p_inspect_sub.add_parser(
|
|
664
|
+
'undeclared',
|
|
665
|
+
aliases=['un'],
|
|
666
|
+
description='Identify components present in code but not declared in SBOM files',
|
|
667
|
+
help='Find undeclared components (legacy format)',
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
# SBOM format option for legacy undeclared components inspection
|
|
671
|
+
p_inspect_legacy_undeclared.add_argument(
|
|
672
|
+
'--sbom-format',
|
|
673
|
+
required=False,
|
|
674
|
+
choices=['legacy', 'settings'],
|
|
675
|
+
default='settings',
|
|
676
|
+
help='SBOM format type for comparison: legacy or settings (default)',
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Legacy license summary inspection - backward compatibility for 'scanoss-py inspect license-summary'
|
|
680
|
+
p_inspect_legacy_license_summary = p_inspect_sub.add_parser(
|
|
681
|
+
'license-summary',
|
|
682
|
+
aliases=['lic-summary', 'licsum'],
|
|
683
|
+
description='Generate comprehensive summary of all licenses found in scan results',
|
|
684
|
+
help='Generate license summary report (legacy format)',
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
# Legacy component summary inspection - backward compatibility for 'scanoss-py inspect component-summary'
|
|
688
|
+
p_inspect_legacy_component_summary = p_inspect_sub.add_parser(
|
|
689
|
+
'component-summary',
|
|
690
|
+
aliases=['comp-summary', 'compsum'],
|
|
691
|
+
description='Generate comprehensive summary of all components found in scan results',
|
|
692
|
+
help='Generate component summary report (legacy format)',
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# Applies the same configuration to both legacy and raw versions
|
|
696
|
+
# License filtering options - common to (legacy) copyleft and license summary commands
|
|
697
|
+
for p in [
|
|
698
|
+
p_inspect_raw_copyleft,
|
|
699
|
+
p_inspect_raw_license_summary,
|
|
700
|
+
p_inspect_legacy_copyleft,
|
|
701
|
+
p_inspect_legacy_license_summary,
|
|
702
|
+
]:
|
|
703
|
+
p.add_argument('--include', help='Additional licenses to include in analysis (comma-separated list)')
|
|
704
|
+
p.add_argument('--exclude', help='Licenses to exclude from analysis (comma-separated list)')
|
|
705
|
+
p.add_argument('--explicit', help='Use only these specific licenses for analysis (comma-separated list)')
|
|
706
|
+
|
|
707
|
+
# License source filtering
|
|
708
|
+
for p in [p_inspect_raw_copyleft, p_inspect_legacy_copyleft]:
|
|
709
|
+
p.add_argument(
|
|
710
|
+
'-ls', '--license-sources',
|
|
711
|
+
action='extend',
|
|
712
|
+
nargs='+',
|
|
713
|
+
choices=VALID_LICENSE_SOURCES,
|
|
714
|
+
help=f'Specify which license sources to check for copyleft violations. Each license object in scan results '
|
|
715
|
+
f'has a source field indicating its origin. Default: {", ".join(DEFAULT_COPYLEFT_LICENSE_SOURCES)}',
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
# Common options for (legacy) copyleft and undeclared component inspection
|
|
719
|
+
for p in [p_inspect_raw_copyleft, p_inspect_raw_undeclared, p_inspect_legacy_copyleft, p_inspect_legacy_undeclared]:
|
|
720
|
+
p.add_argument('-i', '--input', nargs='?', help='Path to scan results file to analyse')
|
|
721
|
+
p.add_argument(
|
|
722
|
+
'-f',
|
|
723
|
+
'--format',
|
|
724
|
+
required=False,
|
|
725
|
+
choices=['json', 'md', 'jira_md'],
|
|
726
|
+
default='json',
|
|
727
|
+
help='Output format: json (default), md (Markdown), or jira_md (JIRA Markdown)',
|
|
728
|
+
)
|
|
729
|
+
p.add_argument('-o', '--output', type=str, help='Save detailed results to specified file')
|
|
730
|
+
p.add_argument('-s', '--status', type=str, help='Save summary status report to Markdown file')
|
|
731
|
+
|
|
732
|
+
# Common options for (legacy) license and component summary commands
|
|
733
|
+
for p in [
|
|
734
|
+
p_inspect_raw_license_summary,
|
|
735
|
+
p_inspect_raw_component_summary,
|
|
736
|
+
p_inspect_legacy_license_summary,
|
|
737
|
+
p_inspect_legacy_component_summary,
|
|
738
|
+
]:
|
|
739
|
+
p.add_argument('-i', '--input', nargs='?', help='Path to scan results file to analyse')
|
|
740
|
+
p.add_argument('-o', '--output', type=str, help='Save summary report to specified file')
|
|
741
|
+
|
|
742
|
+
# -------------------------------------------------------------------------
|
|
743
|
+
# DEPENDENCY TRACK INSPECTION - Analyse Dependency Track project data
|
|
744
|
+
# -------------------------------------------------------------------------
|
|
745
|
+
|
|
746
|
+
# Dependency Track parser - handles inspection of DT project status and violations
|
|
747
|
+
p_dep_track_sub = p_inspect_sub.add_parser(
|
|
748
|
+
'dependency-track',
|
|
749
|
+
aliases=['dt'],
|
|
750
|
+
description='Inspect and analyse Dependency Track project status and policy violations',
|
|
751
|
+
help='Analyse Dependency Track projects',
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# Dependency Track sub-commands parser
|
|
755
|
+
p_inspect_dep_track_sub = p_dep_track_sub.add_subparsers(
|
|
756
|
+
title='Dependency Track Inspection Commands',
|
|
757
|
+
dest='subparser_subcmd',
|
|
758
|
+
description='Tools for analysing Dependency Track project data',
|
|
759
|
+
help='Choose a Dependency Track analysis type',
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Project violations inspection - analyses policy violations in DT projects
|
|
763
|
+
p_inspect_dt_project_violation = p_inspect_dep_track_sub.add_parser(
|
|
764
|
+
'project-violations',
|
|
765
|
+
aliases=['pv'],
|
|
766
|
+
description='Analyse policy violations and compliance issues in Dependency Track projects',
|
|
767
|
+
help='Inspect project policy violations',
|
|
768
|
+
)
|
|
769
|
+
# Dependency Track connection and authentication options
|
|
770
|
+
p_inspect_dt_project_violation.add_argument(
|
|
771
|
+
'--url', required=True, type=str, help='Dependency Track server base URL (e.g., https://dtrack.example.com)'
|
|
772
|
+
)
|
|
773
|
+
p_inspect_dt_project_violation.add_argument(
|
|
774
|
+
'--upload-token',
|
|
775
|
+
'-ut',
|
|
776
|
+
required=False,
|
|
777
|
+
type=str,
|
|
778
|
+
help='Project-specific upload token for accessing DT project data',
|
|
779
|
+
)
|
|
780
|
+
p_inspect_dt_project_violation.add_argument(
|
|
781
|
+
'--project-id', '-pid', required=False, type=str, help='Dependency Track project UUID to inspect'
|
|
782
|
+
)
|
|
783
|
+
p_inspect_dt_project_violation.add_argument(
|
|
784
|
+
'--apikey', '-k', required=True, type=str, help='Dependency Track API key for authentication'
|
|
785
|
+
)
|
|
786
|
+
p_inspect_dt_project_violation.add_argument(
|
|
787
|
+
'--project-name', '-pn', required=False, type=str, help='Dependency Track project name'
|
|
788
|
+
)
|
|
789
|
+
p_inspect_dt_project_violation.add_argument(
|
|
790
|
+
'--project-version', '-pv', required=False, type=str, help='Dependency Track project version'
|
|
791
|
+
)
|
|
792
|
+
p_inspect_dt_project_violation.add_argument(
|
|
793
|
+
'--output', '-o', required=False, type=str, help='Save inspection results to specified file'
|
|
794
|
+
)
|
|
795
|
+
p_inspect_dt_project_violation.add_argument(
|
|
796
|
+
'--status', required=False, type=str, help='Save summary status report to specified file'
|
|
797
|
+
)
|
|
798
|
+
p_inspect_dt_project_violation.add_argument(
|
|
799
|
+
'--format',
|
|
800
|
+
'-f',
|
|
801
|
+
required=False,
|
|
802
|
+
choices=['json', 'md', 'jira_md'],
|
|
803
|
+
default='json',
|
|
804
|
+
help='Output format: json (default), md (Markdown) or jira_md (JIRA Markdown)',
|
|
805
|
+
)
|
|
806
|
+
p_inspect_dt_project_violation.add_argument(
|
|
807
|
+
'--timeout',
|
|
808
|
+
'-M',
|
|
809
|
+
required=False,
|
|
810
|
+
default=300,
|
|
811
|
+
type=float,
|
|
812
|
+
help='Timeout (in seconds) for API communication (optional - default 300 sec)',
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# ==============================================================================
|
|
816
|
+
# GitLab Integration Parser
|
|
817
|
+
# ==============================================================================
|
|
818
|
+
# Main parser for GitLab-specific inspection commands and report generation
|
|
819
|
+
p_gitlab_sub = p_inspect_sub.add_parser(
|
|
820
|
+
'gitlab',
|
|
821
|
+
aliases=['glc'],
|
|
822
|
+
description='Generate GitLab-compatible reports from SCANOSS scan results (Markdown summaries)',
|
|
823
|
+
help='Generate GitLab integration reports',
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
# GitLab sub-commands parser
|
|
827
|
+
# Provides access to different GitLab report formats and inspection tools
|
|
828
|
+
p_gitlab_sub_parser = p_gitlab_sub.add_subparsers(
|
|
829
|
+
title='GitLab Report Types',
|
|
830
|
+
dest='subparser_subcmd',
|
|
831
|
+
description='Available GitLab report formats for scan result analysis',
|
|
832
|
+
help='Select the type of GitLab report to generate',
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
# ==============================================================================
|
|
836
|
+
# GitLab Matches Summary Command
|
|
837
|
+
# ==============================================================================
|
|
838
|
+
# Analyzes scan results and generates a GitLab-compatible Markdown summary
|
|
839
|
+
p_gl_inspect_matches = p_gitlab_sub_parser.add_parser(
|
|
840
|
+
'matches',
|
|
841
|
+
aliases=['ms'],
|
|
842
|
+
description='Generate a Markdown summary report of scan matches for GitLab integration',
|
|
843
|
+
help='Generate Markdown summary report of scan matches',
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
# Input file argument - SCANOSS scan results in JSON format
|
|
847
|
+
p_gl_inspect_matches.add_argument(
|
|
848
|
+
'-i', '--input', required=True, type=str, help='Path to SCANOSS scan results file (JSON format) to analyze'
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
# Line range prefix for GitLab file navigation
|
|
852
|
+
# Enables clickable file references in the generated report that link to specific lines in GitLab
|
|
853
|
+
p_gl_inspect_matches.add_argument(
|
|
854
|
+
'-lpr',
|
|
855
|
+
'--line-range-prefix',
|
|
856
|
+
required=True,
|
|
857
|
+
type=str,
|
|
858
|
+
help='Base URL prefix for GitLab file links with line ranges (e.g., https://gitlab.com/org/project/-/blob/main)',
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
# Output file argument - where to save the generated Markdown report
|
|
862
|
+
p_gl_inspect_matches.add_argument(
|
|
863
|
+
'--output',
|
|
864
|
+
'-o',
|
|
865
|
+
required=False,
|
|
866
|
+
type=str,
|
|
867
|
+
help='Output file path for the generated Markdown report (default: stdout)',
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# TODO Move to the command call def location
|
|
871
|
+
# RAW results
|
|
872
|
+
p_inspect_raw_undeclared.set_defaults(func=inspect_undeclared)
|
|
873
|
+
p_inspect_raw_copyleft.set_defaults(func=inspect_copyleft)
|
|
874
|
+
p_inspect_raw_license_summary.set_defaults(func=inspect_license_summary)
|
|
875
|
+
p_inspect_raw_component_summary.set_defaults(func=inspect_component_summary)
|
|
876
|
+
# Legacy backward compatibility commands
|
|
877
|
+
p_inspect_legacy_copyleft.set_defaults(func=inspect_copyleft)
|
|
878
|
+
p_inspect_legacy_undeclared.set_defaults(func=inspect_undeclared)
|
|
879
|
+
p_inspect_legacy_license_summary.set_defaults(func=inspect_license_summary)
|
|
880
|
+
p_inspect_legacy_component_summary.set_defaults(func=inspect_component_summary)
|
|
881
|
+
# Dependency Track
|
|
882
|
+
p_inspect_dt_project_violation.set_defaults(func=inspect_dep_track_project_violations)
|
|
883
|
+
# GitLab
|
|
884
|
+
p_gl_inspect_matches.set_defaults(func=inspect_gitlab_matches)
|
|
885
|
+
|
|
886
|
+
# =========================================================================
|
|
887
|
+
# END INSPECT SUBCOMMAND CONFIGURATION
|
|
888
|
+
# =========================================================================
|
|
889
|
+
|
|
890
|
+
# Sub-command: export
|
|
891
|
+
p_export = subparsers.add_parser(
|
|
892
|
+
'export',
|
|
893
|
+
aliases=['exp'],
|
|
894
|
+
description=f'Export SBOM files to external platforms: {__version__}',
|
|
895
|
+
help='Export SBOM files to external platforms',
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
export_sub = p_export.add_subparsers(
|
|
899
|
+
title='Export Commands',
|
|
900
|
+
dest='subparsercmd',
|
|
901
|
+
description='export sub-commands',
|
|
902
|
+
help='export sub-commands',
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
# Export Sub-command: export dt (Dependency Track)
|
|
906
|
+
e_dt = export_sub.add_parser(
|
|
907
|
+
'dt',
|
|
908
|
+
aliases=['dependency-track'],
|
|
909
|
+
description='Export SBOM to Dependency Track',
|
|
910
|
+
help='Upload SBOM files to Dependency Track',
|
|
911
|
+
)
|
|
912
|
+
e_dt.add_argument('-i', '--input', type=str, required=True, help='Input SBOM file (CycloneDX JSON format)')
|
|
913
|
+
e_dt.add_argument('--url', type=str, required=True, help='Dependency Track base URL')
|
|
914
|
+
e_dt.add_argument('--apikey', '-k', type=str, required=True, help='Dependency Track API key')
|
|
915
|
+
e_dt.add_argument('--output', '-o', type=str, help='File to save export token and uuid into')
|
|
916
|
+
e_dt.add_argument('--project-id', '-pid', type=str, help='Dependency Track project UUID')
|
|
917
|
+
e_dt.add_argument('--project-name', '-pn', type=str, help='Dependency Track project name')
|
|
918
|
+
e_dt.add_argument('--project-version', '-pv', type=str, help='Dependency Track project version')
|
|
919
|
+
e_dt.set_defaults(func=export_dt)
|
|
920
|
+
|
|
921
|
+
# Sub-command: folder-scan
|
|
922
|
+
p_folder_scan = subparsers.add_parser(
|
|
923
|
+
'folder-scan',
|
|
924
|
+
aliases=['fs'],
|
|
925
|
+
description=f'Scan the given directory using folder hashing: {__version__}',
|
|
926
|
+
help='Scan the given directory using folder hashing',
|
|
927
|
+
)
|
|
928
|
+
p_folder_scan.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='The root directory to scan')
|
|
929
|
+
p_folder_scan.add_argument(
|
|
930
|
+
'--timeout',
|
|
931
|
+
'-M',
|
|
932
|
+
type=int,
|
|
933
|
+
default=600,
|
|
934
|
+
help='Timeout (in seconds) for API communication (optional - default 600)',
|
|
935
|
+
)
|
|
936
|
+
p_folder_scan.add_argument(
|
|
937
|
+
'--format',
|
|
938
|
+
'-f',
|
|
939
|
+
type=str,
|
|
940
|
+
choices=['json', 'cyclonedx'],
|
|
941
|
+
default='json',
|
|
942
|
+
help='Result output format (optional - default: json)',
|
|
943
|
+
)
|
|
944
|
+
p_folder_scan.add_argument(
|
|
945
|
+
'--rank-threshold',
|
|
946
|
+
type=int,
|
|
947
|
+
default=DEFAULT_HFH_RANK_THRESHOLD,
|
|
948
|
+
help='Filter results to only show those with rank value at or below this threshold (e.g., --rank-threshold 3 '
|
|
949
|
+
'returns results with rank 1, 2, or 3). Lower rank values indicate higher quality matches.',
|
|
950
|
+
)
|
|
951
|
+
p_folder_scan.add_argument(
|
|
952
|
+
'--depth',
|
|
953
|
+
type=int,
|
|
954
|
+
default=DEFAULT_HFH_DEPTH,
|
|
955
|
+
help=f'Defines how deep to scan the root directory (optional - default {DEFAULT_HFH_DEPTH})',
|
|
956
|
+
)
|
|
957
|
+
p_folder_scan.add_argument(
|
|
958
|
+
'--recursive-threshold',
|
|
959
|
+
type=float,
|
|
960
|
+
default=DEFAULT_HFH_RECURSIVE_THRESHOLD,
|
|
961
|
+
help=f'Minimum score threshold to consider a match (optional - default: {DEFAULT_HFH_RECURSIVE_THRESHOLD})',
|
|
962
|
+
)
|
|
963
|
+
p_folder_scan.add_argument(
|
|
964
|
+
'--min-accepted-score',
|
|
965
|
+
type=float,
|
|
966
|
+
default=DEFAULT_HFH_MIN_ACCEPTED_SCORE,
|
|
967
|
+
help=(
|
|
968
|
+
'Only show results with a score at or above this threshold '
|
|
969
|
+
f'(optional - default: {DEFAULT_HFH_MIN_ACCEPTED_SCORE})'
|
|
970
|
+
),
|
|
971
|
+
)
|
|
972
|
+
p_folder_scan.set_defaults(func=folder_hashing_scan)
|
|
973
|
+
|
|
974
|
+
# Sub-command: folder-hash
|
|
975
|
+
p_folder_hash = subparsers.add_parser(
|
|
976
|
+
'folder-hash',
|
|
977
|
+
aliases=['fh'],
|
|
978
|
+
description=f'Produce a folder hash for the given directory: {__version__}',
|
|
979
|
+
help='Produce a folder hash for the given directory',
|
|
980
|
+
)
|
|
981
|
+
p_folder_hash.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
|
|
982
|
+
p_folder_hash.add_argument(
|
|
983
|
+
'--format',
|
|
984
|
+
'-f',
|
|
985
|
+
type=str,
|
|
986
|
+
choices=['json'],
|
|
987
|
+
default='json',
|
|
988
|
+
help='Result output format (optional - default: json)',
|
|
989
|
+
)
|
|
990
|
+
p_folder_hash.add_argument(
|
|
991
|
+
'--depth',
|
|
992
|
+
type=int,
|
|
993
|
+
default=DEFAULT_HFH_DEPTH,
|
|
994
|
+
help=f'Defines how deep to hash the root directory (optional - default {DEFAULT_HFH_DEPTH})',
|
|
995
|
+
)
|
|
996
|
+
p_folder_hash.set_defaults(func=folder_hash)
|
|
997
|
+
|
|
998
|
+
# Sub-command: delta
|
|
999
|
+
p_delta = subparsers.add_parser(
|
|
1000
|
+
'delta',
|
|
1001
|
+
aliases=['dl'],
|
|
1002
|
+
description=f'SCANOSS Delta commands: {__version__}',
|
|
1003
|
+
help='Delta support commands',
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
delta_sub = p_delta.add_subparsers(
|
|
1007
|
+
title='Delta Commands', dest='subparsercmd', description='Delta sub-commands', help='Delta sub-commands'
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
# Delta Sub-command: copy
|
|
1011
|
+
p_copy = delta_sub.add_parser(
|
|
1012
|
+
'copy',
|
|
1013
|
+
aliases=['cp'],
|
|
1014
|
+
description=f'Copy file list into delta dir: {__version__}',
|
|
1015
|
+
help='Copy the given list of files into a delta directory',
|
|
1016
|
+
)
|
|
1017
|
+
p_copy.add_argument('--input', '-i', type=str, required=True, help='Input file with diff list')
|
|
1018
|
+
p_copy.add_argument('--folder', '-fd', type=str, help='Delta folder to copy into')
|
|
1019
|
+
p_copy.add_argument('--root', '-rd', type=str, help='Root directory to place delta folder')
|
|
1020
|
+
p_copy.set_defaults(func=delta_copy)
|
|
1021
|
+
|
|
1022
|
+
# Output options
|
|
1023
|
+
for p in [
|
|
1024
|
+
p_scan,
|
|
1025
|
+
p_cs,
|
|
1026
|
+
p_wfp,
|
|
1027
|
+
p_dep,
|
|
1028
|
+
p_fc,
|
|
1029
|
+
p_cnv,
|
|
1030
|
+
c_vulns,
|
|
1031
|
+
c_search,
|
|
1032
|
+
c_versions,
|
|
1033
|
+
c_semgrep,
|
|
1034
|
+
c_provenance,
|
|
1035
|
+
p_c_dwnld,
|
|
1036
|
+
p_folder_scan,
|
|
1037
|
+
p_folder_hash,
|
|
1038
|
+
p_crypto_algorithms,
|
|
1039
|
+
p_crypto_hints,
|
|
1040
|
+
p_crypto_versions_in_range,
|
|
1041
|
+
c_licenses,
|
|
1042
|
+
p_copy,
|
|
1043
|
+
]:
|
|
1044
|
+
p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
1045
|
+
|
|
1046
|
+
# Format options
|
|
1047
|
+
for p in [p_scan, p_cs]:
|
|
1048
|
+
choices = ['plain', 'cyclonedx', 'spdxlite', 'csv']
|
|
1049
|
+
if p is p_cs:
|
|
1050
|
+
choices.append('raw')
|
|
1051
|
+
|
|
1052
|
+
p.add_argument(
|
|
1053
|
+
'--format',
|
|
1054
|
+
'-f',
|
|
1055
|
+
type=str,
|
|
1056
|
+
choices=choices,
|
|
1057
|
+
default='plain',
|
|
1058
|
+
help='Result output format (optional - default: plain)',
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
# Scanoss settings options
|
|
1062
|
+
for p in [p_folder_scan, p_scan, p_wfp, p_folder_hash]:
|
|
1063
|
+
p.add_argument(
|
|
1064
|
+
'--settings',
|
|
1065
|
+
'-st',
|
|
1066
|
+
type=str,
|
|
1067
|
+
help='Settings file to use for scanning (optional - default scanoss.json)',
|
|
1068
|
+
)
|
|
1069
|
+
p.add_argument(
|
|
1070
|
+
'--skip-settings-file',
|
|
1071
|
+
'-stf',
|
|
1072
|
+
action='store_true',
|
|
1073
|
+
help='Skip default settings file (scanoss.json) if it exists',
|
|
1074
|
+
)
|
|
244
1075
|
|
|
245
1076
|
# Global Scan command options
|
|
246
|
-
for p in [p_scan]:
|
|
247
|
-
p.add_argument(
|
|
248
|
-
|
|
249
|
-
|
|
1077
|
+
for p in [p_scan, p_cs]:
|
|
1078
|
+
p.add_argument(
|
|
1079
|
+
'--apiurl', type=str, help='SCANOSS API URL (optional - default: https://api.osskb.org/scan/direct)'
|
|
1080
|
+
)
|
|
250
1081
|
|
|
251
1082
|
# Global Scan/Fingerprint filter options
|
|
252
1083
|
for p in [p_scan, p_wfp]:
|
|
253
1084
|
p.add_argument('--obfuscate', action='store_true', help='Obfuscate fingerprints')
|
|
254
|
-
p.add_argument('--all-extensions', action='store_true', help='Fingerprint all file extensions')
|
|
255
|
-
p.add_argument('--all-folders', action='store_true', help='Fingerprint all folders')
|
|
256
|
-
p.add_argument('--all-hidden', action='store_true', help='Fingerprint all hidden files/folders')
|
|
1085
|
+
p.add_argument('--all-extensions', action='store_true', help='Fingerprint all file extensions/types...')
|
|
1086
|
+
p.add_argument('--all-folders', action='store_true', help='Fingerprint all folders...')
|
|
1087
|
+
p.add_argument('--all-hidden', action='store_true', help='Fingerprint all hidden files/folders...')
|
|
257
1088
|
p.add_argument('--hpsm', '-H', action='store_true', help='Use High Precision Snippet Matching algorithm.')
|
|
258
1089
|
p.add_argument('--skip-snippets', '-S', action='store_true', help='Skip the generation of snippets')
|
|
259
1090
|
p.add_argument('--skip-extension', '-E', type=str, action='append', help='File Extension to skip.')
|
|
260
1091
|
p.add_argument('--skip-folder', '-O', type=str, action='append', help='Folder to skip.')
|
|
261
|
-
p.add_argument(
|
|
262
|
-
|
|
1092
|
+
p.add_argument(
|
|
1093
|
+
'--skip-size',
|
|
1094
|
+
'-Z',
|
|
1095
|
+
type=int,
|
|
1096
|
+
default=0,
|
|
1097
|
+
help='Minimum file size to consider for fingerprinting (optional - default 0 bytes [unlimited])',
|
|
1098
|
+
)
|
|
263
1099
|
p.add_argument('--skip-md5', '-5', type=str, action='append', help='Skip files matching MD5.')
|
|
264
1100
|
p.add_argument('--strip-hpsm', '-G', type=str, action='append', help='Strip HPSM string from WFP.')
|
|
265
1101
|
p.add_argument('--strip-snippet', '-N', type=str, action='append', help='Strip Snippet ID string from WFP.')
|
|
1102
|
+
p.add_argument(
|
|
1103
|
+
'--skip-headers',
|
|
1104
|
+
'-skh',
|
|
1105
|
+
action='store_true',
|
|
1106
|
+
help='Skip license headers, comments and imports at the beginning of files.',
|
|
1107
|
+
)
|
|
1108
|
+
p.add_argument(
|
|
1109
|
+
'--skip-headers-limit',
|
|
1110
|
+
'-shl',
|
|
1111
|
+
type=int,
|
|
1112
|
+
default=0,
|
|
1113
|
+
help='Maximum number of lines to skip when filtering headers (default: 0 = no limit).',
|
|
1114
|
+
)
|
|
266
1115
|
|
|
267
1116
|
# Global Scan/GRPC options
|
|
268
|
-
for p in [
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1117
|
+
for p in [
|
|
1118
|
+
p_scan,
|
|
1119
|
+
c_vulns,
|
|
1120
|
+
c_search,
|
|
1121
|
+
c_versions,
|
|
1122
|
+
c_semgrep,
|
|
1123
|
+
c_provenance,
|
|
1124
|
+
p_folder_scan,
|
|
1125
|
+
p_cs,
|
|
1126
|
+
p_crypto_algorithms,
|
|
1127
|
+
p_crypto_hints,
|
|
1128
|
+
p_crypto_versions_in_range,
|
|
1129
|
+
c_licenses,
|
|
1130
|
+
]:
|
|
1131
|
+
p.add_argument(
|
|
1132
|
+
'--key', '-k', type=str, help='SCANOSS API Key token (optional - not required for default OSSKB URL)'
|
|
1133
|
+
)
|
|
1134
|
+
p.add_argument(
|
|
1135
|
+
'--proxy',
|
|
1136
|
+
type=str,
|
|
1137
|
+
help='Proxy URL to use for connections (optional). '
|
|
1138
|
+
'Can also use the environment variable "HTTPS_PROXY=<ip>:<port>" '
|
|
1139
|
+
'and "grcp_proxy=<ip>:<port>" for gRPC',
|
|
1140
|
+
)
|
|
1141
|
+
p.add_argument(
|
|
1142
|
+
'--pac',
|
|
1143
|
+
type=str,
|
|
1144
|
+
help='Proxy auto configuration (optional). Specify a file, http url or "auto" to try to discover it.',
|
|
1145
|
+
)
|
|
1146
|
+
p.add_argument(
|
|
1147
|
+
'--ca-cert',
|
|
1148
|
+
type=str,
|
|
1149
|
+
help='Alternative certificate PEM file (optional). '
|
|
1150
|
+
'Can also use the environment variable '
|
|
1151
|
+
'"REQUESTS_CA_BUNDLE=/path/to/cacert.pem" and '
|
|
1152
|
+
'"GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/path/to/cacert.pem" for gRPC',
|
|
1153
|
+
)
|
|
280
1154
|
|
|
281
1155
|
# Global GRPC options
|
|
282
|
-
for p in [
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
1156
|
+
for p in [
|
|
1157
|
+
p_scan,
|
|
1158
|
+
c_vulns,
|
|
1159
|
+
c_search,
|
|
1160
|
+
c_versions,
|
|
1161
|
+
c_semgrep,
|
|
1162
|
+
c_provenance,
|
|
1163
|
+
p_folder_scan,
|
|
1164
|
+
p_cs,
|
|
1165
|
+
p_crypto_algorithms,
|
|
1166
|
+
p_crypto_hints,
|
|
1167
|
+
p_crypto_versions_in_range,
|
|
1168
|
+
c_licenses,
|
|
1169
|
+
]:
|
|
1170
|
+
p.add_argument(
|
|
1171
|
+
'--api2url', type=str, help='SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)'
|
|
1172
|
+
)
|
|
1173
|
+
p.add_argument(
|
|
1174
|
+
'--grpc-proxy',
|
|
1175
|
+
type=str,
|
|
1176
|
+
help='GRPC Proxy URL to use for connections (optional). '
|
|
1177
|
+
'Can also use the environment variable "grcp_proxy=<ip>:<port>"',
|
|
1178
|
+
)
|
|
1179
|
+
p.add_argument(
|
|
1180
|
+
'--header',
|
|
1181
|
+
'-hdr',
|
|
1182
|
+
action='append', # This allows multiple -H flags
|
|
1183
|
+
type=str,
|
|
1184
|
+
help='Headers to be sent on request (e.g., -hdr "Name: Value") - can be used multiple times',
|
|
1185
|
+
)
|
|
1186
|
+
p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors')
|
|
1187
|
+
|
|
1188
|
+
# Syft options
|
|
1189
|
+
for p in [p_cs, p_dep]:
|
|
1190
|
+
p.add_argument(
|
|
1191
|
+
'--syft-command',
|
|
1192
|
+
type=str,
|
|
1193
|
+
help='Syft command and path if required (optional - default syft).',
|
|
1194
|
+
default=DEFAULT_SYFT_COMMAND,
|
|
1195
|
+
)
|
|
1196
|
+
p.add_argument(
|
|
1197
|
+
'--syft-timeout',
|
|
1198
|
+
type=int,
|
|
1199
|
+
default=DEFAULT_SYFT_TIMEOUT,
|
|
1200
|
+
help='Timeout (in seconds) for syft to complete (optional - default 600)',
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
# gRPC support options
|
|
1204
|
+
for p in [
|
|
1205
|
+
c_vulns,
|
|
1206
|
+
p_scan,
|
|
1207
|
+
p_cs,
|
|
1208
|
+
p_crypto_algorithms,
|
|
1209
|
+
p_crypto_hints,
|
|
1210
|
+
p_crypto_versions_in_range,
|
|
1211
|
+
c_semgrep,
|
|
1212
|
+
c_provenance,
|
|
1213
|
+
c_search,
|
|
1214
|
+
c_versions,
|
|
1215
|
+
c_licenses,
|
|
1216
|
+
p_folder_scan,
|
|
1217
|
+
]:
|
|
1218
|
+
p.add_argument('--grpc', action='store_true', default=True, help='Use gRPC (default)')
|
|
1219
|
+
p.add_argument('--rest', action='store_true', dest='rest', help='Use REST instead of gRPC')
|
|
287
1220
|
|
|
288
1221
|
# Help/Trace command options
|
|
289
|
-
for p in [
|
|
290
|
-
|
|
291
|
-
|
|
1222
|
+
for p in [
|
|
1223
|
+
p_scan,
|
|
1224
|
+
p_wfp,
|
|
1225
|
+
p_dep,
|
|
1226
|
+
p_fc,
|
|
1227
|
+
p_cnv,
|
|
1228
|
+
p_c_loc,
|
|
1229
|
+
p_c_dwnld,
|
|
1230
|
+
p_p_proxy,
|
|
1231
|
+
c_vulns,
|
|
1232
|
+
c_search,
|
|
1233
|
+
c_versions,
|
|
1234
|
+
c_semgrep,
|
|
1235
|
+
p_results,
|
|
1236
|
+
p_inspect_raw_undeclared,
|
|
1237
|
+
p_inspect_raw_copyleft,
|
|
1238
|
+
p_inspect_raw_license_summary,
|
|
1239
|
+
p_inspect_raw_component_summary,
|
|
1240
|
+
p_inspect_legacy_copyleft,
|
|
1241
|
+
p_inspect_legacy_undeclared,
|
|
1242
|
+
p_inspect_legacy_license_summary,
|
|
1243
|
+
p_inspect_legacy_component_summary,
|
|
1244
|
+
p_inspect_dt_project_violation,
|
|
1245
|
+
p_gl_inspect_matches,
|
|
1246
|
+
c_provenance,
|
|
1247
|
+
p_folder_scan,
|
|
1248
|
+
p_folder_hash,
|
|
1249
|
+
p_cs,
|
|
1250
|
+
p_crypto_algorithms,
|
|
1251
|
+
p_crypto_hints,
|
|
1252
|
+
p_crypto_versions_in_range,
|
|
1253
|
+
c_licenses,
|
|
1254
|
+
e_dt,
|
|
1255
|
+
p_copy,
|
|
1256
|
+
]:
|
|
1257
|
+
p.add_argument(
|
|
1258
|
+
'--debug',
|
|
1259
|
+
'-d',
|
|
1260
|
+
action='store_true',
|
|
1261
|
+
default=os.environ.get('SCANOSS_DEBUG', '').lower() == 'true',
|
|
1262
|
+
help='Enable debug messages (can also be set via environment variable SCANOSS_DEBUG)',
|
|
1263
|
+
)
|
|
292
1264
|
p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
|
|
293
1265
|
p.add_argument('--quiet', '-q', action='store_true', help='Enable quiet mode')
|
|
294
1266
|
|
|
295
1267
|
args = parser.parse_args()
|
|
1268
|
+
|
|
1269
|
+
# TODO: Remove this hack once we go back to using REST as default
|
|
1270
|
+
# Handle --rest overriding --grpc default
|
|
1271
|
+
if hasattr(args, 'rest') and args.rest:
|
|
1272
|
+
args.grpc = False
|
|
1273
|
+
|
|
296
1274
|
if args.version:
|
|
297
1275
|
ver(parser, args)
|
|
298
|
-
exit(0)
|
|
1276
|
+
sys.exit(0)
|
|
299
1277
|
if not args.subparser:
|
|
300
1278
|
parser.print_help() # No sub command subcommand, print general help
|
|
301
|
-
exit(1)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
1279
|
+
sys.exit(1)
|
|
1280
|
+
elif (
|
|
1281
|
+
args.subparser
|
|
1282
|
+
in (
|
|
1283
|
+
'utils',
|
|
1284
|
+
'ut',
|
|
1285
|
+
'component',
|
|
1286
|
+
'comp',
|
|
1287
|
+
'inspect',
|
|
1288
|
+
'insp',
|
|
1289
|
+
'ins',
|
|
1290
|
+
'crypto',
|
|
1291
|
+
'cr',
|
|
1292
|
+
'export',
|
|
1293
|
+
'exp',
|
|
1294
|
+
'delta',
|
|
1295
|
+
'dl',
|
|
1296
|
+
)
|
|
1297
|
+
) and not args.subparsercmd:
|
|
1298
|
+
parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
|
|
1299
|
+
sys.exit(1)
|
|
1300
|
+
elif (
|
|
1301
|
+
(args.subparser in 'inspect')
|
|
1302
|
+
and (args.subparsercmd in ('raw', 'dt', 'glc', 'gitlab'))
|
|
1303
|
+
and (args.subparser_subcmd is None)
|
|
1304
|
+
):
|
|
1305
|
+
parser.parse_args([args.subparser, args.subparsercmd, '--help']) # Force utils helps to be displayed
|
|
1306
|
+
sys.exit(1)
|
|
308
1307
|
args.func(parser, args) # Execute the function associated with the sub-command
|
|
309
1308
|
|
|
310
1309
|
|
|
@@ -337,23 +1336,25 @@ def file_count(parser, args):
|
|
|
337
1336
|
if not args.scan_dir:
|
|
338
1337
|
print_stderr('Please specify a folder')
|
|
339
1338
|
parser.parse_args([args.subparser, '-h'])
|
|
340
|
-
exit(1)
|
|
341
|
-
scan_output: str = None
|
|
1339
|
+
sys.exit(1)
|
|
342
1340
|
if args.output:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
1341
|
+
initialise_empty_file(args.output)
|
|
1342
|
+
|
|
1343
|
+
counter = FileCount(
|
|
1344
|
+
debug=args.debug,
|
|
1345
|
+
quiet=args.quiet,
|
|
1346
|
+
trace=args.trace,
|
|
1347
|
+
scan_output=args.output,
|
|
1348
|
+
hidden_files_folders=args.all_hidden,
|
|
1349
|
+
)
|
|
349
1350
|
if not os.path.exists(args.scan_dir):
|
|
350
1351
|
print_stderr(f'Error: Folder specified does not exist: {args.scan_dir}.')
|
|
351
|
-
exit(1)
|
|
1352
|
+
sys.exit(1)
|
|
352
1353
|
if os.path.isdir(args.scan_dir):
|
|
353
1354
|
counter.count_files(args.scan_dir)
|
|
354
1355
|
else:
|
|
355
1356
|
print_stderr(f'Error: Path specified is not a folder: {args.scan_dir}.')
|
|
356
|
-
exit(1)
|
|
1357
|
+
sys.exit(1)
|
|
357
1358
|
|
|
358
1359
|
|
|
359
1360
|
def wfp(parser, args):
|
|
@@ -369,38 +1370,60 @@ def wfp(parser, args):
|
|
|
369
1370
|
if not args.scan_dir and not args.stdin:
|
|
370
1371
|
print_stderr('Please specify a file/folder or STDIN (--stdin)')
|
|
371
1372
|
parser.parse_args([args.subparser, '-h'])
|
|
372
|
-
exit(1)
|
|
1373
|
+
sys.exit(1)
|
|
373
1374
|
if args.strip_hpsm and not args.hpsm and not args.quiet:
|
|
374
|
-
print_stderr(
|
|
375
|
-
scan_output: str = None
|
|
1375
|
+
print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
|
|
376
1376
|
if args.output:
|
|
377
|
-
|
|
378
|
-
|
|
1377
|
+
initialise_empty_file(args.output)
|
|
1378
|
+
|
|
1379
|
+
# Load scan settings
|
|
1380
|
+
scan_settings = None
|
|
1381
|
+
if not args.skip_settings_file:
|
|
1382
|
+
scan_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1383
|
+
try:
|
|
1384
|
+
scan_settings.load_json_file(args.settings, args.scan_dir)
|
|
1385
|
+
except ScanossSettingsError as e:
|
|
1386
|
+
print_stderr(f'Error: {e}')
|
|
1387
|
+
sys.exit(1)
|
|
379
1388
|
|
|
380
1389
|
scan_options = 0 if args.skip_snippets else ScanType.SCAN_SNIPPETS.value # Skip snippet generation or not
|
|
381
|
-
scanner = Scanner(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1390
|
+
scanner = Scanner(
|
|
1391
|
+
debug=args.debug,
|
|
1392
|
+
trace=args.trace,
|
|
1393
|
+
quiet=args.quiet,
|
|
1394
|
+
obfuscate=args.obfuscate,
|
|
1395
|
+
scan_options=scan_options,
|
|
1396
|
+
all_extensions=args.all_extensions,
|
|
1397
|
+
all_folders=args.all_folders,
|
|
1398
|
+
hidden_files_folders=args.all_hidden,
|
|
1399
|
+
hpsm=args.hpsm,
|
|
1400
|
+
skip_size=args.skip_size,
|
|
1401
|
+
skip_extensions=args.skip_extension,
|
|
1402
|
+
skip_folders=args.skip_folder,
|
|
1403
|
+
skip_md5_ids=args.skip_md5,
|
|
1404
|
+
strip_hpsm_ids=args.strip_hpsm,
|
|
1405
|
+
strip_snippet_ids=args.strip_snippet,
|
|
1406
|
+
scan_settings=scan_settings,
|
|
1407
|
+
skip_headers=args.skip_headers,
|
|
1408
|
+
skip_headers_limit=args.skip_headers_limit,
|
|
1409
|
+
)
|
|
387
1410
|
if args.stdin:
|
|
388
1411
|
contents = sys.stdin.buffer.read()
|
|
389
|
-
scanner.wfp_contents(args.stdin, contents,
|
|
1412
|
+
scanner.wfp_contents(args.stdin, contents, args.output)
|
|
390
1413
|
elif args.scan_dir:
|
|
391
1414
|
if not os.path.exists(args.scan_dir):
|
|
392
1415
|
print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
|
|
393
|
-
exit(1)
|
|
1416
|
+
sys.exit(1)
|
|
394
1417
|
if os.path.isdir(args.scan_dir):
|
|
395
|
-
scanner.wfp_folder(args.scan_dir,
|
|
1418
|
+
scanner.wfp_folder(args.scan_dir, args.output)
|
|
396
1419
|
elif os.path.isfile(args.scan_dir):
|
|
397
|
-
scanner.wfp_file(args.scan_dir,
|
|
1420
|
+
scanner.wfp_file(args.scan_dir, args.output)
|
|
398
1421
|
else:
|
|
399
1422
|
print_stderr(f'Error: Path specified is neither a file or a folder: {args.scan_dir}.')
|
|
400
|
-
exit(1)
|
|
1423
|
+
sys.exit(1)
|
|
401
1424
|
else:
|
|
402
1425
|
print_stderr('No action found to process')
|
|
403
|
-
exit(1)
|
|
1426
|
+
sys.exit(1)
|
|
404
1427
|
|
|
405
1428
|
|
|
406
1429
|
def get_scan_options(args):
|
|
@@ -424,18 +1447,18 @@ def get_scan_options(args):
|
|
|
424
1447
|
|
|
425
1448
|
if args.debug:
|
|
426
1449
|
if ScanType.SCAN_FILES.value & scan_options:
|
|
427
|
-
print_stderr(
|
|
1450
|
+
print_stderr('Scan Files')
|
|
428
1451
|
if ScanType.SCAN_SNIPPETS.value & scan_options:
|
|
429
|
-
print_stderr(
|
|
1452
|
+
print_stderr('Scan Snippets')
|
|
430
1453
|
if ScanType.SCAN_DEPENDENCIES.value & scan_options:
|
|
431
|
-
print_stderr(
|
|
1454
|
+
print_stderr('Scan Dependencies')
|
|
432
1455
|
if scan_options <= 0:
|
|
433
1456
|
print_stderr(f'Error: No valid scan options configured: {scan_options}')
|
|
434
|
-
exit(1)
|
|
1457
|
+
sys.exit(1)
|
|
435
1458
|
return scan_options
|
|
436
1459
|
|
|
437
1460
|
|
|
438
|
-
def scan(parser, args):
|
|
1461
|
+
def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
439
1462
|
"""
|
|
440
1463
|
Run the "scan" sub-command
|
|
441
1464
|
Parameters
|
|
@@ -445,66 +1468,77 @@ def scan(parser, args):
|
|
|
445
1468
|
args: Namespace
|
|
446
1469
|
Parsed arguments
|
|
447
1470
|
"""
|
|
448
|
-
if not args.scan_dir and not args.wfp and not args.stdin and not args.dep:
|
|
449
|
-
print_stderr(
|
|
1471
|
+
if not args.scan_dir and not args.wfp and not args.stdin and not args.dep and not args.files:
|
|
1472
|
+
print_stderr(
|
|
1473
|
+
'Please specify a file/folder, files (--files), fingerprint (--wfp), dependency (--dep), or STDIN (--stdin)'
|
|
1474
|
+
)
|
|
450
1475
|
parser.parse_args([args.subparser, '-h'])
|
|
451
|
-
exit(1)
|
|
1476
|
+
sys.exit(1)
|
|
1477
|
+
if args.no_wfp_output:
|
|
1478
|
+
print_stderr('Warning: --no-wfp-output is deprecated and has no effect. It will be removed in a future version')
|
|
452
1479
|
if args.pac and args.proxy:
|
|
453
1480
|
print_stderr('Please specify one of --proxy or --pac, not both')
|
|
454
1481
|
parser.parse_args([args.subparser, '-h'])
|
|
455
|
-
exit(1)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1482
|
+
sys.exit(1)
|
|
1483
|
+
if args.identify and args.settings:
|
|
1484
|
+
print_stderr('ERROR: Cannot specify both --identify and --settings options.')
|
|
1485
|
+
sys.exit(1)
|
|
1486
|
+
if args.settings and args.skip_settings_file:
|
|
1487
|
+
print_stderr('ERROR: Cannot specify both --settings and --skip-file-settings options.')
|
|
1488
|
+
sys.exit(1)
|
|
1489
|
+
# Figure out which settings (if any) to load before processing
|
|
1490
|
+
scan_settings = None
|
|
1491
|
+
if not args.skip_settings_file:
|
|
1492
|
+
scan_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1493
|
+
try:
|
|
1494
|
+
if args.identify:
|
|
1495
|
+
scan_settings.load_json_file(args.identify, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
1496
|
+
'identify'
|
|
1497
|
+
)
|
|
1498
|
+
elif args.ignore:
|
|
1499
|
+
scan_settings.load_json_file(args.ignore, args.scan_dir).set_file_type('legacy').set_scan_type(
|
|
1500
|
+
'blacklist'
|
|
1501
|
+
)
|
|
1502
|
+
else:
|
|
1503
|
+
scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new')
|
|
1504
|
+
|
|
1505
|
+
except ScanossSettingsError as e:
|
|
1506
|
+
print_stderr(f'Error: {e}')
|
|
1507
|
+
sys.exit(1)
|
|
476
1508
|
if args.dep:
|
|
477
1509
|
if not os.path.exists(args.dep) or not os.path.isfile(args.dep):
|
|
478
1510
|
print_stderr(f'Specified --dep file does not exist or is not a file: {args.dep}')
|
|
479
|
-
exit(1)
|
|
480
|
-
|
|
481
|
-
|
|
1511
|
+
sys.exit(1)
|
|
1512
|
+
result = validate_json_file(args.dep)
|
|
1513
|
+
if not result.is_valid:
|
|
1514
|
+
print_stderr(f'Error: Dependency file is not valid: {result.error}')
|
|
1515
|
+
sys.exit(1)
|
|
482
1516
|
if args.strip_hpsm and not args.hpsm and not args.quiet:
|
|
483
|
-
print_stderr(
|
|
1517
|
+
print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
|
|
484
1518
|
|
|
485
|
-
scan_output: str = None
|
|
486
1519
|
if args.output:
|
|
487
|
-
|
|
488
|
-
open(scan_output, 'w').close()
|
|
1520
|
+
initialise_empty_file(args.output)
|
|
489
1521
|
output_format = args.format if args.format else 'plain'
|
|
490
1522
|
flags = args.flags if args.flags else None
|
|
491
1523
|
if args.debug and not args.quiet:
|
|
1524
|
+
if args.skip_settings_file:
|
|
1525
|
+
print_stderr('Skipping Settings file...')
|
|
492
1526
|
if args.all_extensions:
|
|
493
|
-
print_stderr(
|
|
1527
|
+
print_stderr('Scanning all file extensions/types...')
|
|
494
1528
|
if args.all_folders:
|
|
495
|
-
print_stderr(
|
|
1529
|
+
print_stderr('Scanning all folders...')
|
|
496
1530
|
if args.all_hidden:
|
|
497
|
-
print_stderr(
|
|
1531
|
+
print_stderr('Scanning all hidden files/folders...')
|
|
498
1532
|
if args.skip_snippets:
|
|
499
|
-
print_stderr(
|
|
500
|
-
if args.post_size !=
|
|
1533
|
+
print_stderr('Skipping snippets...')
|
|
1534
|
+
if args.post_size != DEFAULT_POST_SIZE:
|
|
501
1535
|
print_stderr(f'Changing scanning POST size to: {args.post_size}k...')
|
|
502
|
-
if args.timeout !=
|
|
1536
|
+
if args.timeout != DEFAULT_TIMEOUT:
|
|
503
1537
|
print_stderr(f'Changing scanning POST timeout to: {args.timeout}...')
|
|
504
|
-
if args.retry !=
|
|
1538
|
+
if args.retry != DEFAULT_RETRY:
|
|
505
1539
|
print_stderr(f'Changing scanning POST retry to: {args.retry}...')
|
|
506
1540
|
if args.obfuscate:
|
|
507
|
-
print_stderr(
|
|
1541
|
+
print_stderr('Obfuscating file fingerprints...')
|
|
508
1542
|
if args.proxy:
|
|
509
1543
|
print_stderr(f'Using Proxy {args.proxy}...')
|
|
510
1544
|
if args.grpc_proxy:
|
|
@@ -514,70 +1548,116 @@ def scan(parser, args):
|
|
|
514
1548
|
if args.ca_cert:
|
|
515
1549
|
print_stderr(f'Using Certificate {args.ca_cert}...')
|
|
516
1550
|
if args.hpsm:
|
|
517
|
-
print_stderr(
|
|
1551
|
+
print_stderr('Setting HPSM mode...')
|
|
518
1552
|
if flags:
|
|
519
1553
|
print_stderr(f'Using flags {flags}...')
|
|
520
1554
|
elif not args.quiet:
|
|
521
|
-
if args.timeout <
|
|
1555
|
+
if args.timeout < MIN_TIMEOUT:
|
|
522
1556
|
print_stderr(f'POST timeout (--timeout) too small: {args.timeout}. Reverting to default.')
|
|
523
1557
|
if args.retry < 0:
|
|
524
1558
|
print_stderr(f'POST retry (--retry) too small: {args.retry}. Reverting to default.')
|
|
525
1559
|
|
|
526
|
-
if not os.access(os.getcwd(), os.W_OK): # Make sure the current directory is writable. If not disable saving WFP
|
|
527
|
-
print_stderr(f'Warning: Current directory is not writable: {os.getcwd()}')
|
|
528
|
-
args.no_wfp_output = True
|
|
529
1560
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
530
1561
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
531
|
-
exit(1)
|
|
1562
|
+
sys.exit(1)
|
|
532
1563
|
pac_file = get_pac_file(args.pac)
|
|
533
|
-
scan_options = get_scan_options(args)
|
|
534
|
-
|
|
535
|
-
scanner = Scanner(
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1564
|
+
scan_options = get_scan_options(args) # Figure out what scanning options we have
|
|
1565
|
+
|
|
1566
|
+
scanner = Scanner(
|
|
1567
|
+
debug=args.debug,
|
|
1568
|
+
trace=args.trace,
|
|
1569
|
+
quiet=args.quiet,
|
|
1570
|
+
api_key=args.key,
|
|
1571
|
+
url=args.apiurl,
|
|
1572
|
+
scan_output=args.output,
|
|
1573
|
+
output_format=output_format,
|
|
1574
|
+
flags=flags,
|
|
1575
|
+
nb_threads=args.threads,
|
|
1576
|
+
post_size=args.post_size,
|
|
1577
|
+
timeout=args.timeout,
|
|
1578
|
+
all_extensions=args.all_extensions,
|
|
1579
|
+
all_folders=args.all_folders,
|
|
1580
|
+
hidden_files_folders=args.all_hidden,
|
|
1581
|
+
scan_options=scan_options,
|
|
1582
|
+
sc_timeout=args.sc_timeout,
|
|
1583
|
+
sc_command=args.sc_command,
|
|
1584
|
+
grpc_url=args.api2url,
|
|
1585
|
+
obfuscate=args.obfuscate,
|
|
1586
|
+
ignore_cert_errors=args.ignore_cert_errors,
|
|
1587
|
+
proxy=args.proxy,
|
|
1588
|
+
grpc_proxy=args.grpc_proxy,
|
|
1589
|
+
pac=pac_file,
|
|
1590
|
+
ca_cert=args.ca_cert,
|
|
1591
|
+
retry=args.retry,
|
|
1592
|
+
hpsm=args.hpsm,
|
|
1593
|
+
skip_size=args.skip_size,
|
|
1594
|
+
skip_extensions=args.skip_extension,
|
|
1595
|
+
skip_folders=args.skip_folder,
|
|
1596
|
+
skip_md5_ids=args.skip_md5,
|
|
1597
|
+
strip_hpsm_ids=args.strip_hpsm,
|
|
1598
|
+
strip_snippet_ids=args.strip_snippet,
|
|
1599
|
+
scan_settings=scan_settings,
|
|
1600
|
+
req_headers=process_req_headers(args.header),
|
|
1601
|
+
use_grpc=args.grpc,
|
|
1602
|
+
skip_headers=args.skip_headers,
|
|
1603
|
+
skip_headers_limit=args.skip_headers_limit,
|
|
1604
|
+
)
|
|
547
1605
|
if args.wfp:
|
|
548
1606
|
if not scanner.is_file_or_snippet_scan():
|
|
549
1607
|
print_stderr(f'Error: Cannot specify WFP scanning if file/snippet options are disabled ({scan_options})')
|
|
550
|
-
exit(1)
|
|
1608
|
+
sys.exit(1)
|
|
551
1609
|
if scanner.is_dependency_scan() and not args.dep:
|
|
552
|
-
print_stderr(
|
|
553
|
-
exit(1)
|
|
1610
|
+
print_stderr('Error: Cannot specify WFP & Dependency scanning without a dependency file (--dep)')
|
|
1611
|
+
sys.exit(1)
|
|
554
1612
|
scanner.scan_wfp_with_options(args.wfp, args.dep)
|
|
555
1613
|
elif args.stdin:
|
|
556
1614
|
contents = sys.stdin.buffer.read()
|
|
557
1615
|
if not scanner.scan_contents(args.stdin, contents):
|
|
558
|
-
exit(1)
|
|
1616
|
+
sys.exit(1)
|
|
1617
|
+
elif args.files:
|
|
1618
|
+
if not scanner.scan_files_with_options(args.files, args.dep, scanner.winnowing.file_map):
|
|
1619
|
+
sys.exit(1)
|
|
559
1620
|
elif args.scan_dir:
|
|
560
1621
|
if not os.path.exists(args.scan_dir):
|
|
561
1622
|
print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
|
|
562
|
-
exit(1)
|
|
1623
|
+
sys.exit(1)
|
|
563
1624
|
if os.path.isdir(args.scan_dir):
|
|
564
|
-
if not scanner.scan_folder_with_options(
|
|
565
|
-
|
|
1625
|
+
if not scanner.scan_folder_with_options(
|
|
1626
|
+
args.scan_dir,
|
|
1627
|
+
args.dep,
|
|
1628
|
+
scanner.winnowing.file_map,
|
|
1629
|
+
args.dep_scope,
|
|
1630
|
+
args.dep_scope_inc,
|
|
1631
|
+
args.dep_scope_exc,
|
|
1632
|
+
):
|
|
1633
|
+
sys.exit(1)
|
|
566
1634
|
elif os.path.isfile(args.scan_dir):
|
|
567
|
-
if not scanner.scan_file_with_options(
|
|
568
|
-
|
|
1635
|
+
if not scanner.scan_file_with_options(
|
|
1636
|
+
args.scan_dir,
|
|
1637
|
+
args.dep,
|
|
1638
|
+
scanner.winnowing.file_map,
|
|
1639
|
+
args.dep_scope,
|
|
1640
|
+
args.dep_scope_inc,
|
|
1641
|
+
args.dep_scope_exc,
|
|
1642
|
+
):
|
|
1643
|
+
sys.exit(1)
|
|
569
1644
|
else:
|
|
570
1645
|
print_stderr(f'Error: Path specified is neither a file or a folder: {args.scan_dir}.')
|
|
571
|
-
exit(1)
|
|
1646
|
+
sys.exit(1)
|
|
572
1647
|
elif args.dep:
|
|
573
1648
|
if not args.dependencies_only:
|
|
574
|
-
print_stderr(
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
1649
|
+
print_stderr(
|
|
1650
|
+
'Error: No file or folder specified to scan.'
|
|
1651
|
+
' Please add --dependencies-only to decorate dependency file only.'
|
|
1652
|
+
)
|
|
1653
|
+
sys.exit(1)
|
|
1654
|
+
if not scanner.scan_folder_with_options(
|
|
1655
|
+
'.', args.dep, scanner.winnowing.file_map, args.dep_scope, args.dep_scope_inc, args.dep_scope_exc
|
|
1656
|
+
):
|
|
1657
|
+
sys.exit(1)
|
|
578
1658
|
else:
|
|
579
1659
|
print_stderr('No action found to process')
|
|
580
|
-
exit(1)
|
|
1660
|
+
sys.exit(1)
|
|
581
1661
|
|
|
582
1662
|
|
|
583
1663
|
def dependency(parser, args):
|
|
@@ -590,23 +1670,28 @@ def dependency(parser, args):
|
|
|
590
1670
|
args: Namespace
|
|
591
1671
|
Parsed arguments
|
|
592
1672
|
"""
|
|
593
|
-
if not args.
|
|
594
|
-
print_stderr('Please specify a file/folder')
|
|
1673
|
+
if not args.scan_loc and not args.container:
|
|
1674
|
+
print_stderr('Please specify a file/folder or container image')
|
|
595
1675
|
parser.parse_args([args.subparser, '-h'])
|
|
596
|
-
exit(1)
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
1676
|
+
sys.exit(1)
|
|
1677
|
+
|
|
1678
|
+
# Workaround to return syft scan results converted to our dependency output format
|
|
1679
|
+
if args.container:
|
|
1680
|
+
args.scan_loc = args.container
|
|
1681
|
+
return container_scan(parser, args, only_interim_results=True)
|
|
1682
|
+
|
|
1683
|
+
if not os.path.exists(args.scan_loc):
|
|
1684
|
+
print_stderr(f'Error: File or folder specified does not exist: {args.scan_loc}.')
|
|
1685
|
+
sys.exit(1)
|
|
601
1686
|
if args.output:
|
|
602
|
-
|
|
603
|
-
open(scan_output, 'w').close()
|
|
1687
|
+
initialise_empty_file(args.output)
|
|
604
1688
|
|
|
605
|
-
sc_deps = ScancodeDeps(
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if not sc_deps.get_dependencies(what_to_scan=args.
|
|
609
|
-
exit(1)
|
|
1689
|
+
sc_deps = ScancodeDeps(
|
|
1690
|
+
debug=args.debug, quiet=args.quiet, trace=args.trace, sc_command=args.sc_command, timeout=args.sc_timeout
|
|
1691
|
+
)
|
|
1692
|
+
if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=args.output):
|
|
1693
|
+
sys.exit(1)
|
|
1694
|
+
return None
|
|
610
1695
|
|
|
611
1696
|
|
|
612
1697
|
def convert(parser, args):
|
|
@@ -622,27 +1707,437 @@ def convert(parser, args):
|
|
|
622
1707
|
if not args.input:
|
|
623
1708
|
print_stderr('Please specify an input file to convert')
|
|
624
1709
|
parser.parse_args([args.subparser, '-h'])
|
|
625
|
-
exit(1)
|
|
1710
|
+
sys.exit(1)
|
|
626
1711
|
success = False
|
|
627
1712
|
if args.format == 'cyclonedx':
|
|
628
1713
|
if not args.quiet:
|
|
629
|
-
print_stderr(
|
|
1714
|
+
print_stderr('Producing CycloneDX report...')
|
|
630
1715
|
cdx = CycloneDx(debug=args.debug, output_file=args.output)
|
|
631
1716
|
success = cdx.produce_from_file(args.input)
|
|
632
1717
|
elif args.format == 'spdxlite':
|
|
633
1718
|
if not args.quiet:
|
|
634
|
-
print_stderr(
|
|
1719
|
+
print_stderr('Producing SPDX Lite report...')
|
|
635
1720
|
spdxlite = SpdxLite(debug=args.debug, output_file=args.output)
|
|
636
1721
|
success = spdxlite.produce_from_file(args.input)
|
|
637
1722
|
elif args.format == 'csv':
|
|
638
1723
|
if not args.quiet:
|
|
639
|
-
print_stderr(
|
|
1724
|
+
print_stderr('Producing CSV report...')
|
|
640
1725
|
csvo = CsvOutput(debug=args.debug, output_file=args.output)
|
|
641
1726
|
success = csvo.produce_from_file(args.input)
|
|
1727
|
+
elif args.format == 'glc-codequality':
|
|
1728
|
+
if not args.quiet:
|
|
1729
|
+
print_stderr('Producing GitLab code quality report...')
|
|
1730
|
+
glc_code_quality = GitLabQualityReport(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1731
|
+
success = glc_code_quality.produce_from_file(args.input, output_file=args.output)
|
|
642
1732
|
else:
|
|
643
1733
|
print_stderr(f'ERROR: Unknown output format (--format): {args.format}')
|
|
644
1734
|
if not success:
|
|
645
|
-
exit(1)
|
|
1735
|
+
sys.exit(1)
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
# =============================================================================
|
|
1739
|
+
# INSPECT COMMAND HANDLERS - Functions that execute inspection operations
|
|
1740
|
+
# =============================================================================
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
def inspect_copyleft(parser, args):
|
|
1744
|
+
"""
|
|
1745
|
+
Handle copyleft license inspection command.
|
|
1746
|
+
|
|
1747
|
+
Analyses scan results to identify components using copyleft licenses
|
|
1748
|
+
that may require compliance actions such as source code disclosure.
|
|
1749
|
+
|
|
1750
|
+
Parameters
|
|
1751
|
+
----------
|
|
1752
|
+
parser : ArgumentParser
|
|
1753
|
+
Command line parser object for help display
|
|
1754
|
+
args : Namespace
|
|
1755
|
+
Parsed command line arguments containing:
|
|
1756
|
+
- input: Path to scan results file
|
|
1757
|
+
- output: Optional output file path
|
|
1758
|
+
- status: Optional status summary file path
|
|
1759
|
+
- format: Output format (json, md, jira_md)
|
|
1760
|
+
- include/exclude/explicit: License filter options
|
|
1761
|
+
"""
|
|
1762
|
+
# Validate required input file parameter
|
|
1763
|
+
if args.input is None:
|
|
1764
|
+
print_stderr('ERROR: Input file is required for copyleft inspection')
|
|
1765
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1766
|
+
sys.exit(1)
|
|
1767
|
+
# Initialise output file if specified
|
|
1768
|
+
if args.output:
|
|
1769
|
+
initialise_empty_file(args.output)
|
|
1770
|
+
# Initialise status summary file if specified
|
|
1771
|
+
if args.status:
|
|
1772
|
+
initialise_empty_file(args.status)
|
|
1773
|
+
try:
|
|
1774
|
+
# Create and configure copyleft inspector
|
|
1775
|
+
i_copyleft = Copyleft(
|
|
1776
|
+
debug=args.debug,
|
|
1777
|
+
trace=args.trace,
|
|
1778
|
+
quiet=args.quiet,
|
|
1779
|
+
filepath=args.input,
|
|
1780
|
+
format_type=args.format,
|
|
1781
|
+
status=args.status,
|
|
1782
|
+
output=args.output,
|
|
1783
|
+
include=args.include, # Additional licenses to check
|
|
1784
|
+
exclude=args.exclude, # Licenses to ignore
|
|
1785
|
+
explicit=args.explicit, # Explicit license list
|
|
1786
|
+
license_sources=args.license_sources, # License sources to check (list)
|
|
1787
|
+
)
|
|
1788
|
+
# Execute inspection and exit with appropriate status code
|
|
1789
|
+
status, _ = i_copyleft.run()
|
|
1790
|
+
sys.exit(status)
|
|
1791
|
+
except Exception as e:
|
|
1792
|
+
print_stderr(e)
|
|
1793
|
+
if args.debug:
|
|
1794
|
+
traceback.print_exc()
|
|
1795
|
+
sys.exit(1)
|
|
1796
|
+
|
|
1797
|
+
|
|
1798
|
+
def inspect_undeclared(parser, args):
|
|
1799
|
+
"""
|
|
1800
|
+
Handle undeclared components inspection command.
|
|
1801
|
+
|
|
1802
|
+
Analyses scan results to identify components that are present in the
|
|
1803
|
+
codebase but not declared in SBOM or manifest files, which may indicate
|
|
1804
|
+
security or compliance risks.
|
|
1805
|
+
|
|
1806
|
+
Parameters
|
|
1807
|
+
----------
|
|
1808
|
+
parser : ArgumentParser
|
|
1809
|
+
Command line parser object for help display
|
|
1810
|
+
args : Namespace
|
|
1811
|
+
Parsed command line arguments containing:
|
|
1812
|
+
- input: Path to scan results file
|
|
1813
|
+
- output: Optional output file path
|
|
1814
|
+
- status: Optional status summary file path
|
|
1815
|
+
- format: Output format (json, md, jira_md)
|
|
1816
|
+
- sbom_format: SBOM format type (legacy, settings)
|
|
1817
|
+
"""
|
|
1818
|
+
# Validate required input file parameter
|
|
1819
|
+
if args.input is None:
|
|
1820
|
+
print_stderr('ERROR: Input file is required for undeclared component inspection')
|
|
1821
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1822
|
+
sys.exit(1)
|
|
1823
|
+
|
|
1824
|
+
# Initialise output file if specified
|
|
1825
|
+
if args.output:
|
|
1826
|
+
initialise_empty_file(args.output)
|
|
1827
|
+
|
|
1828
|
+
# Initialise status summary file if specified
|
|
1829
|
+
if args.status:
|
|
1830
|
+
initialise_empty_file(args.status)
|
|
1831
|
+
|
|
1832
|
+
try:
|
|
1833
|
+
# Create and configure undeclared component inspector
|
|
1834
|
+
i_undeclared = UndeclaredComponent(
|
|
1835
|
+
debug=args.debug,
|
|
1836
|
+
trace=args.trace,
|
|
1837
|
+
quiet=args.quiet,
|
|
1838
|
+
filepath=args.input,
|
|
1839
|
+
format_type=args.format,
|
|
1840
|
+
status=args.status,
|
|
1841
|
+
output=args.output,
|
|
1842
|
+
sbom_format=args.sbom_format, # Format for SBOM comparison
|
|
1843
|
+
)
|
|
1844
|
+
|
|
1845
|
+
# Execute inspection and exit with appropriate status code
|
|
1846
|
+
status, _ = i_undeclared.run()
|
|
1847
|
+
sys.exit(status)
|
|
1848
|
+
except Exception as e:
|
|
1849
|
+
print_stderr(e)
|
|
1850
|
+
if args.debug:
|
|
1851
|
+
traceback.print_exc()
|
|
1852
|
+
sys.exit(1)
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
def inspect_license_summary(parser, args):
|
|
1856
|
+
"""
|
|
1857
|
+
Handle license summary inspection command.
|
|
1858
|
+
|
|
1859
|
+
Generates comprehensive summary of all licenses detected in scan results,
|
|
1860
|
+
including license counts, risk levels, and compliance recommendations.
|
|
1861
|
+
|
|
1862
|
+
Parameters
|
|
1863
|
+
----------
|
|
1864
|
+
parser : ArgumentParser
|
|
1865
|
+
Command line parser object for help display
|
|
1866
|
+
args : Namespace
|
|
1867
|
+
Parsed command line arguments containing:
|
|
1868
|
+
- input: Path to scan results file
|
|
1869
|
+
- output: Optional output file path
|
|
1870
|
+
- include/exclude/explicit: License filter options
|
|
1871
|
+
"""
|
|
1872
|
+
# Validate required input file parameter
|
|
1873
|
+
if args.input is None:
|
|
1874
|
+
print_stderr('ERROR: Input file is required for license summary')
|
|
1875
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1876
|
+
sys.exit(1)
|
|
1877
|
+
|
|
1878
|
+
# Initialise output file if specified
|
|
1879
|
+
if args.output:
|
|
1880
|
+
initialise_empty_file(args.output)
|
|
1881
|
+
|
|
1882
|
+
# Create and configure license summary generator
|
|
1883
|
+
i_license_summary = LicenseSummary(
|
|
1884
|
+
debug=args.debug,
|
|
1885
|
+
trace=args.trace,
|
|
1886
|
+
quiet=args.quiet,
|
|
1887
|
+
filepath=args.input,
|
|
1888
|
+
output=args.output,
|
|
1889
|
+
include=args.include, # Additional licenses to include
|
|
1890
|
+
exclude=args.exclude, # Licenses to exclude from summary
|
|
1891
|
+
explicit=args.explicit, # Explicit license list to summarize
|
|
1892
|
+
)
|
|
1893
|
+
try:
|
|
1894
|
+
# Execute summary generation
|
|
1895
|
+
i_license_summary.run()
|
|
1896
|
+
except Exception as e:
|
|
1897
|
+
print_stderr(e)
|
|
1898
|
+
if args.debug:
|
|
1899
|
+
traceback.print_exc()
|
|
1900
|
+
sys.exit(1)
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
def inspect_component_summary(parser, args):
|
|
1904
|
+
"""
|
|
1905
|
+
Handle component summary inspection command.
|
|
1906
|
+
|
|
1907
|
+
Generates a comprehensive summary of all components detected in scan results,
|
|
1908
|
+
including component counts, versions, match types, and security information.
|
|
1909
|
+
|
|
1910
|
+
Parameters
|
|
1911
|
+
----------
|
|
1912
|
+
parser : ArgumentParser
|
|
1913
|
+
Command line parser object for help display
|
|
1914
|
+
args : Namespace
|
|
1915
|
+
Parsed command line arguments containing:
|
|
1916
|
+
- input: Path to scan results file
|
|
1917
|
+
- output: Optional output file path
|
|
1918
|
+
"""
|
|
1919
|
+
# Validate required input file parameter
|
|
1920
|
+
if args.input is None:
|
|
1921
|
+
print_stderr('ERROR: Input file is required for component summary')
|
|
1922
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1923
|
+
sys.exit(1)
|
|
1924
|
+
|
|
1925
|
+
# Initialise an output file if specified
|
|
1926
|
+
if args.output:
|
|
1927
|
+
initialise_empty_file(args.output) # Create/clear output file
|
|
1928
|
+
|
|
1929
|
+
# Create and configure component summary generator
|
|
1930
|
+
i_component_summary = ComponentSummary(
|
|
1931
|
+
debug=args.debug,
|
|
1932
|
+
trace=args.trace,
|
|
1933
|
+
quiet=args.quiet,
|
|
1934
|
+
filepath=args.input,
|
|
1935
|
+
output=args.output,
|
|
1936
|
+
)
|
|
1937
|
+
|
|
1938
|
+
try:
|
|
1939
|
+
# Execute summary generation
|
|
1940
|
+
i_component_summary.run()
|
|
1941
|
+
except Exception as e:
|
|
1942
|
+
print_stderr(e)
|
|
1943
|
+
if args.debug:
|
|
1944
|
+
traceback.print_exc()
|
|
1945
|
+
sys.exit(1)
|
|
1946
|
+
|
|
1947
|
+
|
|
1948
|
+
def inspect_dep_track_project_violations(parser, args):
|
|
1949
|
+
"""
|
|
1950
|
+
Handle Dependency Track project inspection command.
|
|
1951
|
+
|
|
1952
|
+
Analyses Dependency Track projects for policy violations, security issues,
|
|
1953
|
+
and compliance status. Connects to DT API to retrieve project data and
|
|
1954
|
+
generate detailed violation reports.
|
|
1955
|
+
|
|
1956
|
+
Parameters
|
|
1957
|
+
----------
|
|
1958
|
+
parser : ArgumentParser
|
|
1959
|
+
Command line parser object for help display
|
|
1960
|
+
args : Namespace
|
|
1961
|
+
Parsed command line arguments containing:
|
|
1962
|
+
- url: Dependency Track base URL
|
|
1963
|
+
- apikey: API key for authentication
|
|
1964
|
+
- project_id: Project UUID to inspect
|
|
1965
|
+
- project_name: Project name to inspect
|
|
1966
|
+
- project_version: Project version to inspect
|
|
1967
|
+
- upload_token: Upload token for project access
|
|
1968
|
+
- output: Optional output file path
|
|
1969
|
+
- format: Output format (json, md)
|
|
1970
|
+
- timeout: Optional timeout for API requests
|
|
1971
|
+
|
|
1972
|
+
"""
|
|
1973
|
+
# Make sure we have project id/project name and version
|
|
1974
|
+
_dt_args_validator(parser, args)
|
|
1975
|
+
# Initialise the output file if specified
|
|
1976
|
+
if args.output:
|
|
1977
|
+
initialise_empty_file(args.output)
|
|
1978
|
+
# Create and configure Dependency Track inspector
|
|
1979
|
+
try:
|
|
1980
|
+
dt_proj_violations = DependencyTrackProjectViolationPolicyCheck(
|
|
1981
|
+
debug=args.debug,
|
|
1982
|
+
trace=args.trace,
|
|
1983
|
+
quiet=args.quiet,
|
|
1984
|
+
output=args.output,
|
|
1985
|
+
status=args.status,
|
|
1986
|
+
format_type=args.format,
|
|
1987
|
+
url=args.url, # DT server URL
|
|
1988
|
+
api_key=args.apikey, # Authentication key
|
|
1989
|
+
project_id=args.project_id, # Target project UUID
|
|
1990
|
+
upload_token=args.upload_token, # Upload access token
|
|
1991
|
+
project_name=args.project_name, # DT project name
|
|
1992
|
+
project_version=args.project_version, # DT project version
|
|
1993
|
+
timeout=args.timeout,
|
|
1994
|
+
)
|
|
1995
|
+
# Execute inspection and exit with appropriate status code
|
|
1996
|
+
status = dt_proj_violations.run()
|
|
1997
|
+
sys.exit(status)
|
|
1998
|
+
except Exception as e:
|
|
1999
|
+
print_stderr(e)
|
|
2000
|
+
if args.debug:
|
|
2001
|
+
traceback.print_exc()
|
|
2002
|
+
sys.exit(1)
|
|
2003
|
+
|
|
2004
|
+
|
|
2005
|
+
def inspect_gitlab_matches(parser, args):
|
|
2006
|
+
"""
|
|
2007
|
+
Handle GitLab matches the summary inspection command.
|
|
2008
|
+
|
|
2009
|
+
Analyzes SCANOSS scan results and generates a GitLab-compatible Markdown summary
|
|
2010
|
+
report of component matches. The report includes match details, file locations,
|
|
2011
|
+
and optionally clickable links to source files in GitLab repositories.
|
|
2012
|
+
|
|
2013
|
+
This command processes SCANOSS scan output and creates human-readable Markdown.
|
|
2014
|
+
|
|
2015
|
+
Parameters
|
|
2016
|
+
----------
|
|
2017
|
+
parser : ArgumentParser
|
|
2018
|
+
Command line parser object for help display
|
|
2019
|
+
args : Namespace
|
|
2020
|
+
Parsed command line arguments containing:
|
|
2021
|
+
- input: Path to SCANOSS scan results file (JSON format) to analyze
|
|
2022
|
+
- line_range_prefix: Base URL prefix for generating GitLab file links with line ranges
|
|
2023
|
+
(e.g., 'https://gitlab.com/org/project/-/blob/main')
|
|
2024
|
+
- output: Optional output file path for the generated Markdown report (default: stdout)
|
|
2025
|
+
- debug: Enable debug output for troubleshooting
|
|
2026
|
+
- trace: Enable trace-level logging
|
|
2027
|
+
- quiet: Suppress informational messages
|
|
2028
|
+
|
|
2029
|
+
Notes
|
|
2030
|
+
-----
|
|
2031
|
+
- The output is formatted in Markdown for optimal display in GitLab
|
|
2032
|
+
- Line range prefix enables clickable file references in the report
|
|
2033
|
+
- If output is not specified, the report is written to stdout
|
|
2034
|
+
"""
|
|
2035
|
+
|
|
2036
|
+
if args.input is None:
|
|
2037
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2038
|
+
sys.exit(1)
|
|
2039
|
+
|
|
2040
|
+
if args.line_range_prefix is None:
|
|
2041
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2042
|
+
sys.exit(1)
|
|
2043
|
+
|
|
2044
|
+
# Initialize output file if specified (create/truncate)
|
|
2045
|
+
if args.output:
|
|
2046
|
+
initialise_empty_file(args.output)
|
|
2047
|
+
|
|
2048
|
+
try:
|
|
2049
|
+
# Create GitLab matches summary generator with configuration
|
|
2050
|
+
match_summary = MatchSummary(
|
|
2051
|
+
debug=args.debug,
|
|
2052
|
+
trace=args.trace,
|
|
2053
|
+
quiet=args.quiet,
|
|
2054
|
+
scanoss_results_path=args.input, # Path to SCANOSS JSON results
|
|
2055
|
+
output=args.output, # Output file path or None for stdout
|
|
2056
|
+
line_range_prefix=args.line_range_prefix, # GitLab URL prefix for file links
|
|
2057
|
+
)
|
|
2058
|
+
|
|
2059
|
+
# Execute the summary generation
|
|
2060
|
+
match_summary.run()
|
|
2061
|
+
except Exception as e:
|
|
2062
|
+
# Handle any errors during report generation
|
|
2063
|
+
print_stderr(e)
|
|
2064
|
+
if args.debug:
|
|
2065
|
+
traceback.print_exc()
|
|
2066
|
+
sys.exit(1)
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
# =============================================================================
|
|
2070
|
+
# END INSPECT COMMAND HANDLERS
|
|
2071
|
+
# =============================================================================
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
def export_dt(parser, args):
|
|
2075
|
+
"""
|
|
2076
|
+
Validates and exports a Software Bill of Materials (SBOM) to a Dependency-Track server.
|
|
2077
|
+
|
|
2078
|
+
Parameters:
|
|
2079
|
+
parser (argparse.ArgumentParser): The argument parser to validate input arguments.
|
|
2080
|
+
args (argparse.Namespace): Parsed arguments passed to the command.
|
|
2081
|
+
|
|
2082
|
+
Raises:
|
|
2083
|
+
SystemExit: If argument validation fails or uploading the SBOM to the Dependency-Track server
|
|
2084
|
+
is unsuccessful.
|
|
2085
|
+
"""
|
|
2086
|
+
# Make sure we have project id/project name and version
|
|
2087
|
+
_dt_args_validator(parser, args)
|
|
2088
|
+
if args.output:
|
|
2089
|
+
initialise_empty_file(args.output)
|
|
2090
|
+
if not args.quiet:
|
|
2091
|
+
print_stderr(f'Outputting export data result to: {args.output}')
|
|
2092
|
+
try:
|
|
2093
|
+
dt_exporter = DependencyTrackExporter(
|
|
2094
|
+
url=args.url,
|
|
2095
|
+
apikey=args.apikey,
|
|
2096
|
+
output=args.output,
|
|
2097
|
+
debug=args.debug,
|
|
2098
|
+
trace=args.trace,
|
|
2099
|
+
quiet=args.quiet,
|
|
2100
|
+
)
|
|
2101
|
+
success = dt_exporter.upload_sbom_file(
|
|
2102
|
+
args.input, args.project_id, args.project_name, args.project_version, args.output
|
|
2103
|
+
)
|
|
2104
|
+
if not success:
|
|
2105
|
+
sys.exit(1)
|
|
2106
|
+
except Exception as e:
|
|
2107
|
+
print_stderr(f'ERROR: {e}')
|
|
2108
|
+
if args.debug:
|
|
2109
|
+
traceback.print_exc()
|
|
2110
|
+
sys.exit(1)
|
|
2111
|
+
|
|
2112
|
+
|
|
2113
|
+
def _dt_args_validator(parser, args):
|
|
2114
|
+
"""
|
|
2115
|
+
Validates command-line arguments related to project identification.
|
|
2116
|
+
|
|
2117
|
+
Parameters
|
|
2118
|
+
----------
|
|
2119
|
+
parser : argparse.ArgumentParser
|
|
2120
|
+
An argument parser instance for handling command-line arguments.
|
|
2121
|
+
args : argparse.Namespace
|
|
2122
|
+
Parsed arguments from the command line containing project-related information.
|
|
2123
|
+
|
|
2124
|
+
Raises
|
|
2125
|
+
------
|
|
2126
|
+
SystemExit
|
|
2127
|
+
If neither a project ID nor the required combination of project name and
|
|
2128
|
+
project version is provided, or if any of the compulsory arguments
|
|
2129
|
+
are missing.
|
|
2130
|
+
"""
|
|
2131
|
+
if not args.project_id and not args.project_name and not args.project_version:
|
|
2132
|
+
print_stderr(
|
|
2133
|
+
'Please specify either a project ID (--project-id) or a project name (--project-name) and '
|
|
2134
|
+
'version (--project-version)'
|
|
2135
|
+
)
|
|
2136
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2137
|
+
sys.exit(1)
|
|
2138
|
+
if not args.project_id and (not args.project_name or not args.project_version):
|
|
2139
|
+
print_stderr('Please supply a project name (--project-name) and version (--project-version)')
|
|
2140
|
+
sys.exit(1)
|
|
646
2141
|
|
|
647
2142
|
|
|
648
2143
|
def utils_certloc(*_):
|
|
@@ -650,20 +2145,22 @@ def utils_certloc(*_):
|
|
|
650
2145
|
Run the "utils certloc" sub-command
|
|
651
2146
|
:param _: ignored/unused
|
|
652
2147
|
"""
|
|
653
|
-
import certifi
|
|
2148
|
+
import certifi # noqa: PLC0415,I001
|
|
2149
|
+
|
|
654
2150
|
print(f'CA Cert File: {certifi.where()}')
|
|
655
2151
|
|
|
656
2152
|
|
|
657
|
-
def utils_cert_download(_, args):
|
|
2153
|
+
def utils_cert_download(_, args): # pylint: disable=PLR0912 # noqa: PLR0912
|
|
658
2154
|
"""
|
|
659
2155
|
Run the "utils cert-download" sub-command
|
|
660
2156
|
:param _: ignore/unused
|
|
661
2157
|
:param args: Parsed arguments
|
|
662
2158
|
"""
|
|
663
|
-
|
|
664
|
-
import
|
|
665
|
-
from
|
|
666
|
-
|
|
2159
|
+
import socket # noqa: PLC0415,I001
|
|
2160
|
+
import traceback # noqa: PLC0415,I001
|
|
2161
|
+
from urllib.parse import urlparse # noqa: PLC0415,I001
|
|
2162
|
+
|
|
2163
|
+
from OpenSSL import SSL, crypto # noqa: PLC0415,I001
|
|
667
2164
|
|
|
668
2165
|
file = sys.stdout
|
|
669
2166
|
if args.output:
|
|
@@ -680,21 +2177,24 @@ def utils_cert_download(_, args):
|
|
|
680
2177
|
certs = conn.get_peer_cert_chain()
|
|
681
2178
|
for index, cert in enumerate(certs):
|
|
682
2179
|
cert_components = dict(cert.get_subject().get_components())
|
|
683
|
-
if sys.version_info[0] >=
|
|
2180
|
+
if sys.version_info[0] >= PYTHON_MAJOR_VERSION:
|
|
684
2181
|
cn = cert_components.get(b'CN')
|
|
685
2182
|
else:
|
|
2183
|
+
# Fallback for Python versions less than PYTHON_MAJOR_VERSION
|
|
686
2184
|
cn = cert_components.get('CN')
|
|
687
2185
|
if not args.quiet:
|
|
688
|
-
print_stderr(f'
|
|
689
|
-
if sys.version_info[0] >=
|
|
690
|
-
print(
|
|
2186
|
+
print_stderr(f'Certificate {index} - CN: {cn}')
|
|
2187
|
+
if sys.version_info[0] >= PYTHON_MAJOR_VERSION:
|
|
2188
|
+
print(
|
|
2189
|
+
(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')).strip(), file=file
|
|
2190
|
+
) # Print the downloaded PEM certificate
|
|
691
2191
|
else:
|
|
692
2192
|
print((crypto.dump_certificate(crypto.FILETYPE_PEM, cert)).strip(), file=file)
|
|
693
2193
|
except SSL.Error as e:
|
|
694
2194
|
print_stderr(f'ERROR: Exception ({e.__class__.__name__}) Downloading certificate from {hostname}:{port} - {e}.')
|
|
695
2195
|
if args.debug:
|
|
696
2196
|
traceback.print_exc()
|
|
697
|
-
exit(1)
|
|
2197
|
+
sys.exit(1)
|
|
698
2198
|
else:
|
|
699
2199
|
if args.output:
|
|
700
2200
|
if args.debug:
|
|
@@ -708,14 +2208,15 @@ def utils_pac_proxy(_, args):
|
|
|
708
2208
|
:param _: ignore/unused
|
|
709
2209
|
:param args: Parsed arguments
|
|
710
2210
|
"""
|
|
711
|
-
from pypac.resolver import ProxyResolver
|
|
2211
|
+
from pypac.resolver import ProxyResolver # noqa: PLC0415,I001
|
|
2212
|
+
|
|
712
2213
|
if not args.pac:
|
|
713
|
-
print_stderr(
|
|
714
|
-
exit(1)
|
|
2214
|
+
print_stderr('Error: No pac file option specified.')
|
|
2215
|
+
sys.exit(1)
|
|
715
2216
|
pac_file = get_pac_file(args.pac)
|
|
716
2217
|
if pac_file is None:
|
|
717
2218
|
print_stderr(f'No proxy configuration for: {args.pac}')
|
|
718
|
-
exit(1)
|
|
2219
|
+
sys.exit(1)
|
|
719
2220
|
resolver = ProxyResolver(pac_file)
|
|
720
2221
|
proxies = resolver.get_proxy_for_requests(args.url)
|
|
721
2222
|
print(f'Proxies: {proxies}\n')
|
|
@@ -732,23 +2233,23 @@ def get_pac_file(pac: str):
|
|
|
732
2233
|
if pac == 'auto':
|
|
733
2234
|
pac_file = pypac.get_pac() # try to determine the PAC file
|
|
734
2235
|
elif pac.startswith('file://'):
|
|
735
|
-
pac_local = pac
|
|
2236
|
+
pac_local = pac[7:] # Remove 'file://' prefix (7 characters)
|
|
736
2237
|
if not os.path.exists(pac_local):
|
|
737
2238
|
print_stderr(f'Error: PAC file does not exist: {pac_local}.')
|
|
738
|
-
exit(1)
|
|
2239
|
+
sys.exit(1)
|
|
739
2240
|
with open(pac_local) as pf:
|
|
740
2241
|
pac_file = pypac.get_pac(js=pf.read())
|
|
741
2242
|
elif pac.startswith('http'):
|
|
742
2243
|
pac_file = pypac.get_pac(url=pac)
|
|
743
2244
|
else:
|
|
744
2245
|
print_stderr(f'Error: Unknown PAC file option: {pac}. Should be one of "auto", "file://", "https://"')
|
|
745
|
-
exit(1)
|
|
2246
|
+
sys.exit(1)
|
|
746
2247
|
return pac_file
|
|
747
2248
|
|
|
748
2249
|
|
|
749
|
-
def
|
|
2250
|
+
def crypto_algorithms(parser, args):
|
|
750
2251
|
"""
|
|
751
|
-
Run the "
|
|
2252
|
+
Run the "crypto algorithms" sub-command
|
|
752
2253
|
Parameters
|
|
753
2254
|
----------
|
|
754
2255
|
parser: ArgumentParser
|
|
@@ -759,16 +2260,117 @@ def comp_crypto(parser, args):
|
|
|
759
2260
|
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
760
2261
|
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
761
2262
|
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
762
|
-
exit(1)
|
|
2263
|
+
sys.exit(1)
|
|
763
2264
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
764
2265
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
765
|
-
exit(1)
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
2266
|
+
sys.exit(1)
|
|
2267
|
+
|
|
2268
|
+
try:
|
|
2269
|
+
config = create_cryptography_config_from_args(args)
|
|
2270
|
+
grpc_config = create_grpc_config_from_args(args)
|
|
2271
|
+
if args.pac:
|
|
2272
|
+
grpc_config.pac = get_pac_file(args.pac)
|
|
2273
|
+
if args.header:
|
|
2274
|
+
grpc_config.req_headers = process_req_headers(args.header)
|
|
2275
|
+
client = ScanossGrpc(**asdict(grpc_config))
|
|
2276
|
+
|
|
2277
|
+
cryptography = Cryptography(config=config, client=client)
|
|
2278
|
+
cryptography.get_algorithms()
|
|
2279
|
+
cryptography.present(output_file=args.output)
|
|
2280
|
+
except ScanossGrpcError as e:
|
|
2281
|
+
print_stderr(f'API ERROR: {e}')
|
|
2282
|
+
sys.exit(1)
|
|
2283
|
+
except Exception as e:
|
|
2284
|
+
if args.debug:
|
|
2285
|
+
import traceback # noqa: PLC0415,I001
|
|
2286
|
+
|
|
2287
|
+
traceback.print_exc()
|
|
2288
|
+
print_stderr(f'ERROR: {e}')
|
|
2289
|
+
sys.exit(1)
|
|
2290
|
+
|
|
2291
|
+
|
|
2292
|
+
def crypto_hints(parser, args):
|
|
2293
|
+
"""
|
|
2294
|
+
Run the "crypto hints" sub-command
|
|
2295
|
+
Parameters
|
|
2296
|
+
----------
|
|
2297
|
+
parser: ArgumentParser
|
|
2298
|
+
command line parser object
|
|
2299
|
+
args: Namespace
|
|
2300
|
+
Parsed arguments
|
|
2301
|
+
"""
|
|
2302
|
+
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
2303
|
+
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
2304
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
2305
|
+
sys.exit(1)
|
|
2306
|
+
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
2307
|
+
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
2308
|
+
sys.exit(1)
|
|
2309
|
+
|
|
2310
|
+
try:
|
|
2311
|
+
config = create_cryptography_config_from_args(args)
|
|
2312
|
+
grpc_config = create_grpc_config_from_args(args)
|
|
2313
|
+
if args.pac:
|
|
2314
|
+
grpc_config.pac = get_pac_file(args.pac)
|
|
2315
|
+
if args.header:
|
|
2316
|
+
grpc_config.req_headers = process_req_headers(args.header)
|
|
2317
|
+
client = ScanossGrpc(**asdict(grpc_config))
|
|
2318
|
+
|
|
2319
|
+
cryptography = Cryptography(config=config, client=client)
|
|
2320
|
+
cryptography.get_encryption_hints()
|
|
2321
|
+
cryptography.present(output_file=args.output)
|
|
2322
|
+
except ScanossGrpcError as e:
|
|
2323
|
+
print_stderr(f'API ERROR: {e}')
|
|
2324
|
+
sys.exit(1)
|
|
2325
|
+
except Exception as e:
|
|
2326
|
+
if args.debug:
|
|
2327
|
+
import traceback # noqa: PLC0415,I001
|
|
2328
|
+
|
|
2329
|
+
traceback.print_exc()
|
|
2330
|
+
print_stderr(f'ERROR: {e}')
|
|
2331
|
+
sys.exit(1)
|
|
2332
|
+
|
|
2333
|
+
|
|
2334
|
+
def crypto_versions_in_range(parser, args):
|
|
2335
|
+
"""
|
|
2336
|
+
Run the "crypto versions-in-range" sub-command
|
|
2337
|
+
Parameters
|
|
2338
|
+
----------
|
|
2339
|
+
parser: ArgumentParser
|
|
2340
|
+
command line parser object
|
|
2341
|
+
args: Namespace
|
|
2342
|
+
Parsed arguments
|
|
2343
|
+
"""
|
|
2344
|
+
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
2345
|
+
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
2346
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
2347
|
+
sys.exit(1)
|
|
2348
|
+
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
2349
|
+
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
2350
|
+
sys.exit(1)
|
|
2351
|
+
|
|
2352
|
+
try:
|
|
2353
|
+
config = create_cryptography_config_from_args(args)
|
|
2354
|
+
grpc_config = create_grpc_config_from_args(args)
|
|
2355
|
+
if args.pac:
|
|
2356
|
+
grpc_config.pac = get_pac_file(args.pac)
|
|
2357
|
+
if args.header:
|
|
2358
|
+
grpc_config.req_headers = process_req_headers(args.header)
|
|
2359
|
+
client = ScanossGrpc(**asdict(grpc_config))
|
|
2360
|
+
|
|
2361
|
+
cryptography = Cryptography(config=config, client=client)
|
|
2362
|
+
cryptography.get_versions_in_range()
|
|
2363
|
+
cryptography.present(output_file=args.output)
|
|
2364
|
+
except ScanossGrpcError as e:
|
|
2365
|
+
print_stderr(f'API ERROR: {e}')
|
|
2366
|
+
sys.exit(1)
|
|
2367
|
+
except Exception as e:
|
|
2368
|
+
if args.debug:
|
|
2369
|
+
import traceback # noqa: PLC0415,I001
|
|
2370
|
+
|
|
2371
|
+
traceback.print_exc()
|
|
2372
|
+
print_stderr(f'ERROR: {e}')
|
|
2373
|
+
sys.exit(1)
|
|
772
2374
|
|
|
773
2375
|
|
|
774
2376
|
def comp_vulns(parser, args):
|
|
@@ -784,16 +2386,29 @@ def comp_vulns(parser, args):
|
|
|
784
2386
|
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
785
2387
|
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
786
2388
|
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
787
|
-
exit(1)
|
|
2389
|
+
sys.exit(1)
|
|
788
2390
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
789
2391
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
790
|
-
exit(1)
|
|
2392
|
+
sys.exit(1)
|
|
791
2393
|
pac_file = get_pac_file(args.pac)
|
|
792
|
-
comps = Components(
|
|
793
|
-
|
|
794
|
-
|
|
2394
|
+
comps = Components(
|
|
2395
|
+
debug=args.debug,
|
|
2396
|
+
trace=args.trace,
|
|
2397
|
+
quiet=args.quiet,
|
|
2398
|
+
grpc_url=args.api2url,
|
|
2399
|
+
api_key=args.key,
|
|
2400
|
+
ca_cert=args.ca_cert,
|
|
2401
|
+
proxy=args.proxy,
|
|
2402
|
+
grpc_proxy=args.grpc_proxy,
|
|
2403
|
+
pac=pac_file,
|
|
2404
|
+
timeout=args.timeout,
|
|
2405
|
+
req_headers=process_req_headers(args.header),
|
|
2406
|
+
ignore_cert_errors=args.ignore_cert_errors,
|
|
2407
|
+
use_grpc=args.grpc,
|
|
2408
|
+
)
|
|
795
2409
|
if not comps.get_vulnerabilities(args.input, args.purl, args.output):
|
|
796
|
-
exit(1)
|
|
2410
|
+
sys.exit(1)
|
|
2411
|
+
|
|
797
2412
|
|
|
798
2413
|
def comp_semgrep(parser, args):
|
|
799
2414
|
"""
|
|
@@ -808,16 +2423,28 @@ def comp_semgrep(parser, args):
|
|
|
808
2423
|
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
809
2424
|
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
810
2425
|
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
811
|
-
exit(1)
|
|
2426
|
+
sys.exit(1)
|
|
812
2427
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
813
2428
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
814
|
-
exit(1)
|
|
2429
|
+
sys.exit(1)
|
|
815
2430
|
pac_file = get_pac_file(args.pac)
|
|
816
|
-
comps = Components(
|
|
817
|
-
|
|
818
|
-
|
|
2431
|
+
comps = Components(
|
|
2432
|
+
debug=args.debug,
|
|
2433
|
+
trace=args.trace,
|
|
2434
|
+
quiet=args.quiet,
|
|
2435
|
+
grpc_url=args.api2url,
|
|
2436
|
+
api_key=args.key,
|
|
2437
|
+
ca_cert=args.ca_cert,
|
|
2438
|
+
proxy=args.proxy,
|
|
2439
|
+
grpc_proxy=args.grpc_proxy,
|
|
2440
|
+
pac=pac_file,
|
|
2441
|
+
timeout=args.timeout,
|
|
2442
|
+
req_headers=process_req_headers(args.header),
|
|
2443
|
+
use_grpc=args.grpc,
|
|
2444
|
+
)
|
|
819
2445
|
if not comps.get_semgrep_details(args.input, args.purl, args.output):
|
|
820
|
-
exit(1)
|
|
2446
|
+
sys.exit(1)
|
|
2447
|
+
|
|
821
2448
|
|
|
822
2449
|
def comp_search(parser, args):
|
|
823
2450
|
"""
|
|
@@ -829,23 +2456,42 @@ def comp_search(parser, args):
|
|
|
829
2456
|
args: Namespace
|
|
830
2457
|
Parsed arguments
|
|
831
2458
|
"""
|
|
832
|
-
if (
|
|
833
|
-
|
|
2459
|
+
if (not args.input and not args.search and not args.vendor and not args.comp) or (
|
|
2460
|
+
args.input and (args.search or args.vendor or args.comp)
|
|
2461
|
+
):
|
|
834
2462
|
print_stderr('Please specify an input file or search terms (--input or --search, or --vendor or --comp.)')
|
|
835
2463
|
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
836
|
-
exit(1)
|
|
2464
|
+
sys.exit(1)
|
|
837
2465
|
|
|
838
2466
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
839
2467
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
840
|
-
exit(1)
|
|
2468
|
+
sys.exit(1)
|
|
841
2469
|
pac_file = get_pac_file(args.pac)
|
|
842
|
-
comps = Components(
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
2470
|
+
comps = Components(
|
|
2471
|
+
debug=args.debug,
|
|
2472
|
+
trace=args.trace,
|
|
2473
|
+
quiet=args.quiet,
|
|
2474
|
+
grpc_url=args.api2url,
|
|
2475
|
+
api_key=args.key,
|
|
2476
|
+
ca_cert=args.ca_cert,
|
|
2477
|
+
proxy=args.proxy,
|
|
2478
|
+
grpc_proxy=args.grpc_proxy,
|
|
2479
|
+
pac=pac_file,
|
|
2480
|
+
timeout=args.timeout,
|
|
2481
|
+
req_headers=process_req_headers(args.header),
|
|
2482
|
+
use_grpc=args.grpc,
|
|
2483
|
+
)
|
|
2484
|
+
if not comps.search_components(
|
|
2485
|
+
args.output,
|
|
2486
|
+
json_file=args.input,
|
|
2487
|
+
search=args.search,
|
|
2488
|
+
vendor=args.vendor,
|
|
2489
|
+
comp=args.comp,
|
|
2490
|
+
package=args.package,
|
|
2491
|
+
limit=args.limit,
|
|
2492
|
+
offset=args.offset,
|
|
2493
|
+
):
|
|
2494
|
+
sys.exit(1)
|
|
849
2495
|
|
|
850
2496
|
|
|
851
2497
|
def comp_versions(parser, args):
|
|
@@ -861,17 +2507,360 @@ def comp_versions(parser, args):
|
|
|
861
2507
|
if (not args.input and not args.purl) or (args.input and args.purl):
|
|
862
2508
|
print_stderr('Please specify an input file or search terms (--input or --purl.)')
|
|
863
2509
|
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
864
|
-
exit(1)
|
|
2510
|
+
sys.exit(1)
|
|
865
2511
|
|
|
866
2512
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
867
2513
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
868
|
-
exit(1)
|
|
2514
|
+
sys.exit(1)
|
|
869
2515
|
pac_file = get_pac_file(args.pac)
|
|
870
|
-
comps = Components(
|
|
871
|
-
|
|
872
|
-
|
|
2516
|
+
comps = Components(
|
|
2517
|
+
debug=args.debug,
|
|
2518
|
+
trace=args.trace,
|
|
2519
|
+
quiet=args.quiet,
|
|
2520
|
+
grpc_url=args.api2url,
|
|
2521
|
+
api_key=args.key,
|
|
2522
|
+
ca_cert=args.ca_cert,
|
|
2523
|
+
proxy=args.proxy,
|
|
2524
|
+
grpc_proxy=args.grpc_proxy,
|
|
2525
|
+
pac=pac_file,
|
|
2526
|
+
timeout=args.timeout,
|
|
2527
|
+
req_headers=process_req_headers(args.header),
|
|
2528
|
+
use_grpc=args.grpc,
|
|
2529
|
+
)
|
|
873
2530
|
if not comps.get_component_versions(args.output, json_file=args.input, purl=args.purl, limit=args.limit):
|
|
874
|
-
exit(1)
|
|
2531
|
+
sys.exit(1)
|
|
2532
|
+
|
|
2533
|
+
|
|
2534
|
+
def comp_provenance(parser, args):
|
|
2535
|
+
"""
|
|
2536
|
+
Run the "component provenance" sub-command
|
|
2537
|
+
Parameters
|
|
2538
|
+
----------
|
|
2539
|
+
parser: ArgumentParser
|
|
2540
|
+
command line parser object
|
|
2541
|
+
args: Namespace
|
|
2542
|
+
Parsed arguments
|
|
2543
|
+
"""
|
|
2544
|
+
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
2545
|
+
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
2546
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
2547
|
+
sys.exit(1)
|
|
2548
|
+
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
2549
|
+
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
2550
|
+
sys.exit(1)
|
|
2551
|
+
pac_file = get_pac_file(args.pac)
|
|
2552
|
+
comps = Components(
|
|
2553
|
+
debug=args.debug,
|
|
2554
|
+
trace=args.trace,
|
|
2555
|
+
quiet=args.quiet,
|
|
2556
|
+
grpc_url=args.api2url,
|
|
2557
|
+
api_key=args.key,
|
|
2558
|
+
ca_cert=args.ca_cert,
|
|
2559
|
+
proxy=args.proxy,
|
|
2560
|
+
grpc_proxy=args.grpc_proxy,
|
|
2561
|
+
pac=pac_file,
|
|
2562
|
+
timeout=args.timeout,
|
|
2563
|
+
req_headers=process_req_headers(args.header),
|
|
2564
|
+
use_grpc=args.grpc,
|
|
2565
|
+
)
|
|
2566
|
+
if not comps.get_provenance_details(args.input, args.purl, args.output, args.origin):
|
|
2567
|
+
sys.exit(1)
|
|
2568
|
+
|
|
2569
|
+
|
|
2570
|
+
def comp_licenses(parser, args):
|
|
2571
|
+
"""
|
|
2572
|
+
Run the "component licenses" sub-command
|
|
2573
|
+
Parameters
|
|
2574
|
+
----------
|
|
2575
|
+
parser: ArgumentParser
|
|
2576
|
+
command line parser object
|
|
2577
|
+
args: Namespace
|
|
2578
|
+
Parsed arguments
|
|
2579
|
+
"""
|
|
2580
|
+
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
2581
|
+
print_stderr('ERROR: Please specify an input file or purl to decorate (--purl or --input)')
|
|
2582
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
2583
|
+
sys.exit(1)
|
|
2584
|
+
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
2585
|
+
print_stderr(f'ERROR: Certificate file does not exist: {args.ca_cert}.')
|
|
2586
|
+
sys.exit(1)
|
|
2587
|
+
pac_file = get_pac_file(args.pac)
|
|
2588
|
+
comps = Components(
|
|
2589
|
+
debug=args.debug,
|
|
2590
|
+
trace=args.trace,
|
|
2591
|
+
quiet=args.quiet,
|
|
2592
|
+
grpc_url=args.api2url,
|
|
2593
|
+
api_key=args.key,
|
|
2594
|
+
ca_cert=args.ca_cert,
|
|
2595
|
+
proxy=args.proxy,
|
|
2596
|
+
grpc_proxy=args.grpc_proxy,
|
|
2597
|
+
pac=pac_file,
|
|
2598
|
+
timeout=args.timeout,
|
|
2599
|
+
req_headers=process_req_headers(args.header),
|
|
2600
|
+
use_grpc=args.grpc,
|
|
2601
|
+
)
|
|
2602
|
+
if not comps.get_licenses(args.input, args.purl, args.output):
|
|
2603
|
+
sys.exit(1)
|
|
2604
|
+
|
|
2605
|
+
|
|
2606
|
+
def results(parser, args):
|
|
2607
|
+
"""
|
|
2608
|
+
Run the "results" sub-command
|
|
2609
|
+
Parameters
|
|
2610
|
+
----------
|
|
2611
|
+
parser: ArgumentParser
|
|
2612
|
+
command line parser object
|
|
2613
|
+
args: Namespace
|
|
2614
|
+
Parsed arguments
|
|
2615
|
+
"""
|
|
2616
|
+
if not args.filepath:
|
|
2617
|
+
print_stderr('ERROR: Please specify a file containing the results')
|
|
2618
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2619
|
+
sys.exit(1)
|
|
2620
|
+
|
|
2621
|
+
file_path = Path(args.filepath).resolve()
|
|
2622
|
+
|
|
2623
|
+
if not file_path.is_file():
|
|
2624
|
+
print_stderr(f'The specified file {args.filepath} does not exist')
|
|
2625
|
+
sys.exit(1)
|
|
2626
|
+
|
|
2627
|
+
results = Results(
|
|
2628
|
+
debug=args.debug,
|
|
2629
|
+
trace=args.trace,
|
|
2630
|
+
quiet=args.quiet,
|
|
2631
|
+
filepath=file_path,
|
|
2632
|
+
match_type=args.match_type,
|
|
2633
|
+
status=args.status,
|
|
2634
|
+
output_file=args.output,
|
|
2635
|
+
output_format=args.format,
|
|
2636
|
+
)
|
|
2637
|
+
|
|
2638
|
+
if args.has_pending:
|
|
2639
|
+
results.get_pending_identifications().present()
|
|
2640
|
+
if results.has_results():
|
|
2641
|
+
sys.exit(1)
|
|
2642
|
+
else:
|
|
2643
|
+
results.apply_filters().present()
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
def process_req_headers(headers_array: List[str]) -> dict:
|
|
2647
|
+
"""
|
|
2648
|
+
Process a list of header strings in the format "Name: Value" into a dictionary.
|
|
2649
|
+
|
|
2650
|
+
Args:
|
|
2651
|
+
headers_array (list): List of header strings from command line args
|
|
2652
|
+
|
|
2653
|
+
Returns:
|
|
2654
|
+
dict: Dictionary of header name-value pairs
|
|
2655
|
+
"""
|
|
2656
|
+
# Check if headers_array is empty
|
|
2657
|
+
if not headers_array:
|
|
2658
|
+
# Array is empty
|
|
2659
|
+
return {}
|
|
2660
|
+
|
|
2661
|
+
dict_headers = {}
|
|
2662
|
+
for header_str in headers_array:
|
|
2663
|
+
# Split each "Name: Value" header
|
|
2664
|
+
parts = header_str.split(':', 1)
|
|
2665
|
+
if len(parts) == HEADER_PARTS_COUNT:
|
|
2666
|
+
name = parts[0].strip()
|
|
2667
|
+
value = parts[1].strip()
|
|
2668
|
+
dict_headers[name] = value
|
|
2669
|
+
return dict_headers
|
|
2670
|
+
|
|
2671
|
+
|
|
2672
|
+
def folder_hashing_scan(parser, args):
|
|
2673
|
+
"""Run the "folder-scan" sub-command
|
|
2674
|
+
|
|
2675
|
+
Args:
|
|
2676
|
+
parser (ArgumentParser): command line parser object
|
|
2677
|
+
args (Namespace): Parsed arguments
|
|
2678
|
+
"""
|
|
2679
|
+
try:
|
|
2680
|
+
if not args.scan_dir:
|
|
2681
|
+
print_stderr('ERROR: Please specify a directory to scan')
|
|
2682
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2683
|
+
sys.exit(1)
|
|
2684
|
+
|
|
2685
|
+
if not os.path.exists(args.scan_dir) or not os.path.isdir(args.scan_dir):
|
|
2686
|
+
print_stderr(f'ERROR: The specified directory {args.scan_dir} does not exist')
|
|
2687
|
+
sys.exit(1)
|
|
2688
|
+
|
|
2689
|
+
scanner_config = create_scanner_config_from_args(args)
|
|
2690
|
+
scanoss_settings = get_scanoss_settings_from_args(args)
|
|
2691
|
+
grpc_config = create_grpc_config_from_args(args)
|
|
2692
|
+
|
|
2693
|
+
client = ScanossGrpc(**asdict(grpc_config))
|
|
2694
|
+
|
|
2695
|
+
scanner = ScannerHFH(
|
|
2696
|
+
scan_dir=args.scan_dir,
|
|
2697
|
+
config=scanner_config,
|
|
2698
|
+
client=client,
|
|
2699
|
+
scanoss_settings=scanoss_settings,
|
|
2700
|
+
rank_threshold=args.rank_threshold,
|
|
2701
|
+
depth=args.depth,
|
|
2702
|
+
recursive_threshold=args.recursive_threshold,
|
|
2703
|
+
min_accepted_score=args.min_accepted_score,
|
|
2704
|
+
use_grpc=args.grpc,
|
|
2705
|
+
)
|
|
2706
|
+
|
|
2707
|
+
if scanner.scan():
|
|
2708
|
+
scanner.present(output_file=args.output, output_format=args.format)
|
|
2709
|
+
except ScanossGrpcError as e:
|
|
2710
|
+
print_stderr(f'ERROR: {e}')
|
|
2711
|
+
sys.exit(1)
|
|
2712
|
+
|
|
2713
|
+
|
|
2714
|
+
def folder_hash(parser, args):
|
|
2715
|
+
"""Run the "folder-hash" sub-command
|
|
2716
|
+
|
|
2717
|
+
Args:
|
|
2718
|
+
parser (ArgumentParser): command line parser object
|
|
2719
|
+
args (Namespace): Parsed arguments
|
|
2720
|
+
"""
|
|
2721
|
+
try:
|
|
2722
|
+
if not args.scan_dir:
|
|
2723
|
+
print_stderr('ERROR: Please specify a directory to scan')
|
|
2724
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2725
|
+
sys.exit(1)
|
|
2726
|
+
|
|
2727
|
+
if not os.path.exists(args.scan_dir) or not os.path.isdir(args.scan_dir):
|
|
2728
|
+
print_stderr(f'ERROR: The specified directory {args.scan_dir} does not exist')
|
|
2729
|
+
sys.exit(1)
|
|
2730
|
+
|
|
2731
|
+
folder_hasher_config = create_folder_hasher_config_from_args(args)
|
|
2732
|
+
scanoss_settings = get_scanoss_settings_from_args(args)
|
|
2733
|
+
|
|
2734
|
+
folder_hasher = FolderHasher(
|
|
2735
|
+
scan_dir=args.scan_dir,
|
|
2736
|
+
config=folder_hasher_config,
|
|
2737
|
+
scanoss_settings=scanoss_settings,
|
|
2738
|
+
depth=args.depth,
|
|
2739
|
+
)
|
|
2740
|
+
|
|
2741
|
+
folder_hasher.hash_directory(args.scan_dir)
|
|
2742
|
+
folder_hasher.present(output_file=args.output, output_format=args.format)
|
|
2743
|
+
except Exception as e:
|
|
2744
|
+
print_stderr(f'ERROR: {e}')
|
|
2745
|
+
sys.exit(1)
|
|
2746
|
+
|
|
2747
|
+
|
|
2748
|
+
def container_scan(parser, args, only_interim_results: bool = False):
|
|
2749
|
+
"""
|
|
2750
|
+
Run the "container-scan" sub-command
|
|
2751
|
+
Parameters
|
|
2752
|
+
----------
|
|
2753
|
+
parser: ArgumentParser
|
|
2754
|
+
command line parser object
|
|
2755
|
+
args: Namespace
|
|
2756
|
+
Parsed arguments
|
|
2757
|
+
"""
|
|
2758
|
+
if not args.scan_loc:
|
|
2759
|
+
print_stderr(
|
|
2760
|
+
'Please specify a container image, Docker tar, OCI tar, OCI directory, SIF Container, or directory to scan'
|
|
2761
|
+
)
|
|
2762
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2763
|
+
sys.exit(1)
|
|
2764
|
+
|
|
2765
|
+
try:
|
|
2766
|
+
config = create_container_scanner_config_from_args(args)
|
|
2767
|
+
config.only_interim_results = only_interim_results
|
|
2768
|
+
container_scanner = ContainerScanner(
|
|
2769
|
+
config=config,
|
|
2770
|
+
what_to_scan=args.scan_loc,
|
|
2771
|
+
)
|
|
2772
|
+
|
|
2773
|
+
container_scanner.scan()
|
|
2774
|
+
if only_interim_results:
|
|
2775
|
+
container_scanner.present(output_file=config.output, output_format='raw')
|
|
2776
|
+
else:
|
|
2777
|
+
container_scanner.decorate_scan_results_with_dependencies()
|
|
2778
|
+
container_scanner.present(output_file=config.output, output_format=config.format)
|
|
2779
|
+
except Exception as e:
|
|
2780
|
+
print_stderr(f'ERROR: {e}')
|
|
2781
|
+
sys.exit(1)
|
|
2782
|
+
|
|
2783
|
+
|
|
2784
|
+
def get_scanoss_settings_from_args(args):
|
|
2785
|
+
scanoss_settings = None
|
|
2786
|
+
if not args.skip_settings_file:
|
|
2787
|
+
scanoss_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
2788
|
+
try:
|
|
2789
|
+
scanoss_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new').set_scan_type('identify')
|
|
2790
|
+
except ScanossSettingsError as e:
|
|
2791
|
+
print_stderr(f'Error: {e}')
|
|
2792
|
+
sys.exit(1)
|
|
2793
|
+
return scanoss_settings
|
|
2794
|
+
|
|
2795
|
+
|
|
2796
|
+
def initialise_empty_file(filename: str):
|
|
2797
|
+
"""
|
|
2798
|
+
Initialises an empty file with the specified name. If the file already exists,
|
|
2799
|
+
it truncates its content. Ensures proper error handling in case of failure.
|
|
2800
|
+
|
|
2801
|
+
Args:
|
|
2802
|
+
filename (str): The name of the file to be initialised.
|
|
2803
|
+
|
|
2804
|
+
Raises:
|
|
2805
|
+
SystemExit: If the file cannot be created or written due to an exception,
|
|
2806
|
+
the function prints an error message and exits the program.
|
|
2807
|
+
|
|
2808
|
+
Note:
|
|
2809
|
+
This function writes an empty file and handles exceptions to ensure the
|
|
2810
|
+
program does not continue execution in case of an error.
|
|
2811
|
+
"""
|
|
2812
|
+
if filename:
|
|
2813
|
+
try:
|
|
2814
|
+
open(filename, 'w').close()
|
|
2815
|
+
except Exception as e:
|
|
2816
|
+
print_stderr(f'Error: Unable to create output file {filename}: {e}')
|
|
2817
|
+
sys.exit(1)
|
|
2818
|
+
|
|
2819
|
+
|
|
2820
|
+
def delta_copy(parser, args):
|
|
2821
|
+
"""
|
|
2822
|
+
Handle delta copy command.
|
|
2823
|
+
|
|
2824
|
+
Copies files listed in an input file to a target directory while preserving
|
|
2825
|
+
their directory structure. Creates a unique delta directory if none is specified.
|
|
2826
|
+
|
|
2827
|
+
Parameters
|
|
2828
|
+
----------
|
|
2829
|
+
parser : ArgumentParser
|
|
2830
|
+
Command line parser object for help display
|
|
2831
|
+
args : Namespace
|
|
2832
|
+
Parsed command line arguments containing:
|
|
2833
|
+
- input: Path to file containing list of files to copy
|
|
2834
|
+
- folder: Optional target directory path
|
|
2835
|
+
- output: Optional output file path
|
|
2836
|
+
"""
|
|
2837
|
+
# Validate required input file parameter
|
|
2838
|
+
if args.input is None:
|
|
2839
|
+
print_stderr('ERROR: Input file is required for copying')
|
|
2840
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
2841
|
+
sys.exit(1)
|
|
2842
|
+
# Initialise output file if specified
|
|
2843
|
+
if args.output:
|
|
2844
|
+
initialise_empty_file(args.output)
|
|
2845
|
+
try:
|
|
2846
|
+
# Create and configure delta copy command
|
|
2847
|
+
delta = Delta(
|
|
2848
|
+
debug=args.debug,
|
|
2849
|
+
trace=args.trace,
|
|
2850
|
+
quiet=args.quiet,
|
|
2851
|
+
filepath=args.input,
|
|
2852
|
+
folder=args.folder,
|
|
2853
|
+
output=args.output,
|
|
2854
|
+
root_dir=args.root,
|
|
2855
|
+
)
|
|
2856
|
+
# Execute copy and exit with appropriate status code
|
|
2857
|
+
status, _ = delta.copy()
|
|
2858
|
+
sys.exit(status)
|
|
2859
|
+
except Exception as e:
|
|
2860
|
+
print_stderr(e)
|
|
2861
|
+
if args.debug:
|
|
2862
|
+
traceback.print_exc()
|
|
2863
|
+
sys.exit(1)
|
|
875
2864
|
|
|
876
2865
|
|
|
877
2866
|
def main():
|
|
@@ -881,5 +2870,5 @@ def main():
|
|
|
881
2870
|
setup_args()
|
|
882
2871
|
|
|
883
2872
|
|
|
884
|
-
if __name__ ==
|
|
2873
|
+
if __name__ == '__main__':
|
|
885
2874
|
main()
|