scanoss 1.38.0__py3-none-any.whl → 1.40.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scanoss/__init__.py +1 -1
- scanoss/cli.py +137 -2
- scanoss/data/build_date.txt +1 -1
- scanoss/gitlabqualityreport.py +185 -0
- scanoss/inspection/dependency_track/project_violation.py +3 -2
- scanoss/inspection/policy_check.py +0 -42
- scanoss/inspection/raw/__init__.py +0 -0
- scanoss/inspection/raw/component_summary.py +53 -2
- scanoss/inspection/raw/copyleft.py +3 -2
- scanoss/inspection/raw/license_summary.py +49 -1
- scanoss/inspection/raw/match_summary.py +290 -0
- scanoss/inspection/raw/raw_base.py +5 -10
- scanoss/inspection/raw/undeclared_component.py +3 -2
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/scanners/folder_hasher.py +1 -0
- scanoss/scanners/scanner_hfh.py +3 -1
- scanoss/scanossgrpc.py +5 -2
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/METADATA +1 -1
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/RECORD +25 -19
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/WHEEL +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/top_level.txt +0 -0
scanoss/__init__.py
CHANGED
scanoss/cli.py
CHANGED
|
@@ -40,6 +40,7 @@ from scanoss.inspection.dependency_track.project_violation import (
|
|
|
40
40
|
)
|
|
41
41
|
from scanoss.inspection.raw.component_summary import ComponentSummary
|
|
42
42
|
from scanoss.inspection.raw.license_summary import LicenseSummary
|
|
43
|
+
from scanoss.inspection.raw.match_summary import MatchSummary
|
|
43
44
|
from scanoss.scanners.container_scanner import (
|
|
44
45
|
DEFAULT_SYFT_COMMAND,
|
|
45
46
|
DEFAULT_SYFT_TIMEOUT,
|
|
@@ -73,6 +74,7 @@ from .constants import (
|
|
|
73
74
|
from .csvoutput import CsvOutput
|
|
74
75
|
from .cyclonedx import CycloneDx
|
|
75
76
|
from .filecount import FileCount
|
|
77
|
+
from .gitlabqualityreport import GitLabQualityReport
|
|
76
78
|
from .inspection.raw.copyleft import Copyleft
|
|
77
79
|
from .inspection.raw.undeclared_component import UndeclaredComponent
|
|
78
80
|
from .results import Results
|
|
@@ -283,7 +285,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
283
285
|
'--format',
|
|
284
286
|
'-f',
|
|
285
287
|
type=str,
|
|
286
|
-
choices=['cyclonedx', 'spdxlite', 'csv'],
|
|
288
|
+
choices=['cyclonedx', 'spdxlite', 'csv', 'glc-codequality'],
|
|
287
289
|
default='spdxlite',
|
|
288
290
|
help='Output format (optional - default: spdxlite)',
|
|
289
291
|
)
|
|
@@ -794,6 +796,61 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
794
796
|
help='Timeout (in seconds) for API communication (optional - default 300 sec)',
|
|
795
797
|
)
|
|
796
798
|
|
|
799
|
+
# ==============================================================================
|
|
800
|
+
# GitLab Integration Parser
|
|
801
|
+
# ==============================================================================
|
|
802
|
+
# Main parser for GitLab-specific inspection commands and report generation
|
|
803
|
+
p_gitlab_sub = p_inspect_sub.add_parser(
|
|
804
|
+
'gitlab',
|
|
805
|
+
aliases=['glc'],
|
|
806
|
+
description='Generate GitLab-compatible reports from SCANOSS scan results (Markdown summaries)',
|
|
807
|
+
help='Generate GitLab integration reports',
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
# GitLab sub-commands parser
|
|
811
|
+
# Provides access to different GitLab report formats and inspection tools
|
|
812
|
+
p_gitlab_sub_parser = p_gitlab_sub.add_subparsers(
|
|
813
|
+
title='GitLab Report Types',
|
|
814
|
+
dest='subparser_subcmd',
|
|
815
|
+
description='Available GitLab report formats for scan result analysis',
|
|
816
|
+
help='Select the type of GitLab report to generate',
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
# ==============================================================================
|
|
820
|
+
# GitLab Matches Summary Command
|
|
821
|
+
# ==============================================================================
|
|
822
|
+
# Analyzes scan results and generates a GitLab-compatible Markdown summary
|
|
823
|
+
p_gl_inspect_matches = p_gitlab_sub_parser.add_parser(
|
|
824
|
+
'matches',
|
|
825
|
+
aliases=['ms'],
|
|
826
|
+
description='Generate a Markdown summary report of scan matches for GitLab integration',
|
|
827
|
+
help='Generate Markdown summary report of scan matches',
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
# Input file argument - SCANOSS scan results in JSON format
|
|
831
|
+
p_gl_inspect_matches.add_argument(
|
|
832
|
+
'-i', '--input', required=True, type=str, help='Path to SCANOSS scan results file (JSON format) to analyze'
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
# Line range prefix for GitLab file navigation
|
|
836
|
+
# Enables clickable file references in the generated report that link to specific lines in GitLab
|
|
837
|
+
p_gl_inspect_matches.add_argument(
|
|
838
|
+
'-lpr',
|
|
839
|
+
'--line-range-prefix',
|
|
840
|
+
required=True,
|
|
841
|
+
type=str,
|
|
842
|
+
help='Base URL prefix for GitLab file links with line ranges (e.g., https://gitlab.com/org/project/-/blob/main)',
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
# Output file argument - where to save the generated Markdown report
|
|
846
|
+
p_gl_inspect_matches.add_argument(
|
|
847
|
+
'--output',
|
|
848
|
+
'-o',
|
|
849
|
+
required=False,
|
|
850
|
+
type=str,
|
|
851
|
+
help='Output file path for the generated Markdown report (default: stdout)',
|
|
852
|
+
)
|
|
853
|
+
|
|
797
854
|
# TODO Move to the command call def location
|
|
798
855
|
# RAW results
|
|
799
856
|
p_inspect_raw_undeclared.set_defaults(func=inspect_undeclared)
|
|
@@ -807,6 +864,8 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
807
864
|
p_inspect_legacy_component_summary.set_defaults(func=inspect_component_summary)
|
|
808
865
|
# Dependency Track
|
|
809
866
|
p_inspect_dt_project_violation.set_defaults(func=inspect_dep_track_project_violations)
|
|
867
|
+
# GitLab
|
|
868
|
+
p_gl_inspect_matches.set_defaults(func=inspect_gitlab_matches)
|
|
810
869
|
|
|
811
870
|
# =========================================================================
|
|
812
871
|
# END INSPECT SUBCOMMAND CONFIGURATION
|
|
@@ -1125,6 +1184,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
1125
1184
|
c_search,
|
|
1126
1185
|
c_versions,
|
|
1127
1186
|
c_licenses,
|
|
1187
|
+
p_folder_scan,
|
|
1128
1188
|
]:
|
|
1129
1189
|
p.add_argument('--grpc', action='store_true', default=True, help='Use gRPC (default)')
|
|
1130
1190
|
p.add_argument('--rest', action='store_true', dest='rest', help='Use REST instead of gRPC')
|
|
@@ -1153,6 +1213,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
1153
1213
|
p_inspect_legacy_license_summary,
|
|
1154
1214
|
p_inspect_legacy_component_summary,
|
|
1155
1215
|
p_inspect_dt_project_violation,
|
|
1216
|
+
p_gl_inspect_matches,
|
|
1156
1217
|
c_provenance,
|
|
1157
1218
|
p_folder_scan,
|
|
1158
1219
|
p_folder_hash,
|
|
@@ -1207,7 +1268,11 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
1207
1268
|
) and not args.subparsercmd:
|
|
1208
1269
|
parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
|
|
1209
1270
|
sys.exit(1)
|
|
1210
|
-
elif (
|
|
1271
|
+
elif (
|
|
1272
|
+
(args.subparser in 'inspect')
|
|
1273
|
+
and (args.subparsercmd in ('raw', 'dt', 'glc', 'gitlab'))
|
|
1274
|
+
and (args.subparser_subcmd is None)
|
|
1275
|
+
):
|
|
1211
1276
|
parser.parse_args([args.subparser, args.subparsercmd, '--help']) # Force utils helps to be displayed
|
|
1212
1277
|
sys.exit(1)
|
|
1213
1278
|
args.func(parser, args) # Execute the function associated with the sub-command
|
|
@@ -1628,6 +1693,11 @@ def convert(parser, args):
|
|
|
1628
1693
|
print_stderr('Producing CSV report...')
|
|
1629
1694
|
csvo = CsvOutput(debug=args.debug, output_file=args.output)
|
|
1630
1695
|
success = csvo.produce_from_file(args.input)
|
|
1696
|
+
elif args.format == 'glc-codequality':
|
|
1697
|
+
if not args.quiet:
|
|
1698
|
+
print_stderr('Producing GitLab code quality report...')
|
|
1699
|
+
glc_code_quality = GitLabQualityReport(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1700
|
+
success = glc_code_quality.produce_from_file(args.input, output_file=args.output)
|
|
1631
1701
|
else:
|
|
1632
1702
|
print_stderr(f'ERROR: Unknown output format (--format): {args.format}')
|
|
1633
1703
|
if not success:
|
|
@@ -1901,6 +1971,70 @@ def inspect_dep_track_project_violations(parser, args):
|
|
|
1901
1971
|
sys.exit(1)
|
|
1902
1972
|
|
|
1903
1973
|
|
|
1974
|
+
def inspect_gitlab_matches(parser, args):
|
|
1975
|
+
"""
|
|
1976
|
+
Handle GitLab matches the summary inspection command.
|
|
1977
|
+
|
|
1978
|
+
Analyzes SCANOSS scan results and generates a GitLab-compatible Markdown summary
|
|
1979
|
+
report of component matches. The report includes match details, file locations,
|
|
1980
|
+
and optionally clickable links to source files in GitLab repositories.
|
|
1981
|
+
|
|
1982
|
+
This command processes SCANOSS scan output and creates human-readable Markdown.
|
|
1983
|
+
|
|
1984
|
+
Parameters
|
|
1985
|
+
----------
|
|
1986
|
+
parser : ArgumentParser
|
|
1987
|
+
Command line parser object for help display
|
|
1988
|
+
args : Namespace
|
|
1989
|
+
Parsed command line arguments containing:
|
|
1990
|
+
- input: Path to SCANOSS scan results file (JSON format) to analyze
|
|
1991
|
+
- line_range_prefix: Base URL prefix for generating GitLab file links with line ranges
|
|
1992
|
+
(e.g., 'https://gitlab.com/org/project/-/blob/main')
|
|
1993
|
+
- output: Optional output file path for the generated Markdown report (default: stdout)
|
|
1994
|
+
- debug: Enable debug output for troubleshooting
|
|
1995
|
+
- trace: Enable trace-level logging
|
|
1996
|
+
- quiet: Suppress informational messages
|
|
1997
|
+
|
|
1998
|
+
Notes
|
|
1999
|
+
-----
|
|
2000
|
+
- The output is formatted in Markdown for optimal display in GitLab
|
|
2001
|
+
- Line range prefix enables clickable file references in the report
|
|
2002
|
+
- If output is not specified, the report is written to stdout
|
|
2003
|
+
"""
|
|
2004
|
+
|
|
2005
|
+
if args.input is None:
|
|
2006
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2007
|
+
sys.exit(1)
|
|
2008
|
+
|
|
2009
|
+
if args.line_range_prefix is None:
|
|
2010
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2011
|
+
sys.exit(1)
|
|
2012
|
+
|
|
2013
|
+
# Initialize output file if specified (create/truncate)
|
|
2014
|
+
if args.output:
|
|
2015
|
+
initialise_empty_file(args.output)
|
|
2016
|
+
|
|
2017
|
+
try:
|
|
2018
|
+
# Create GitLab matches summary generator with configuration
|
|
2019
|
+
match_summary = MatchSummary(
|
|
2020
|
+
debug=args.debug,
|
|
2021
|
+
trace=args.trace,
|
|
2022
|
+
quiet=args.quiet,
|
|
2023
|
+
scanoss_results_path=args.input, # Path to SCANOSS JSON results
|
|
2024
|
+
output=args.output, # Output file path or None for stdout
|
|
2025
|
+
line_range_prefix=args.line_range_prefix, # GitLab URL prefix for file links
|
|
2026
|
+
)
|
|
2027
|
+
|
|
2028
|
+
# Execute the summary generation
|
|
2029
|
+
match_summary.run()
|
|
2030
|
+
except Exception as e:
|
|
2031
|
+
# Handle any errors during report generation
|
|
2032
|
+
print_stderr(e)
|
|
2033
|
+
if args.debug:
|
|
2034
|
+
traceback.print_exc()
|
|
2035
|
+
sys.exit(1)
|
|
2036
|
+
|
|
2037
|
+
|
|
1904
2038
|
# =============================================================================
|
|
1905
2039
|
# END INSPECT COMMAND HANDLERS
|
|
1906
2040
|
# =============================================================================
|
|
@@ -2536,6 +2670,7 @@ def folder_hashing_scan(parser, args):
|
|
|
2536
2670
|
depth=args.depth,
|
|
2537
2671
|
recursive_threshold=args.recursive_threshold,
|
|
2538
2672
|
min_accepted_score=args.min_accepted_score,
|
|
2673
|
+
use_grpc=args.grpc,
|
|
2539
2674
|
)
|
|
2540
2675
|
|
|
2541
2676
|
if scanner.scan():
|
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20251029142146, utime: 1761747706
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
|
|
30
|
+
from .scanossbase import ScanossBase
|
|
31
|
+
from .utils import scanoss_scan_results_utils
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Lines:
|
|
36
|
+
begin: int
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Location:
|
|
40
|
+
path: str
|
|
41
|
+
lines: Lines
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class CodeQuality:
|
|
45
|
+
description: str
|
|
46
|
+
check_name: str
|
|
47
|
+
fingerprint: str
|
|
48
|
+
severity: str
|
|
49
|
+
location: Location
|
|
50
|
+
|
|
51
|
+
def to_dict(self):
|
|
52
|
+
"""Convert to dictionary for JSON serialization."""
|
|
53
|
+
return {
|
|
54
|
+
"description": self.description,
|
|
55
|
+
"check_name": self.check_name,
|
|
56
|
+
"fingerprint": self.fingerprint,
|
|
57
|
+
"severity": self.severity,
|
|
58
|
+
"location": {
|
|
59
|
+
"path": self.location.path,
|
|
60
|
+
"lines": {
|
|
61
|
+
"begin": self.location.lines.begin
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class GitLabQualityReport(ScanossBase):
|
|
67
|
+
"""
|
|
68
|
+
GitLabCodeQuality management class
|
|
69
|
+
Handle all interaction with GitLab Code Quality Report formatting
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, debug: bool = False, trace: bool = False, quiet: bool = False):
|
|
73
|
+
"""
|
|
74
|
+
Initialise the GitLabCodeQuality class
|
|
75
|
+
"""
|
|
76
|
+
super().__init__(debug, trace, quiet)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_code_quality(self, file_name: str, result: dict) -> CodeQuality or None:
|
|
80
|
+
if not result.get('file_hash'):
|
|
81
|
+
self.print_debug(f"Warning: no hash found for result: {result}")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
if result.get('id') == 'file':
|
|
85
|
+
description = f"File match found in: {file_name}"
|
|
86
|
+
return CodeQuality(
|
|
87
|
+
description=description,
|
|
88
|
+
check_name=file_name,
|
|
89
|
+
fingerprint=result.get('file_hash'),
|
|
90
|
+
severity="info",
|
|
91
|
+
location=Location(
|
|
92
|
+
path=file_name,
|
|
93
|
+
lines = Lines(
|
|
94
|
+
begin= 1
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if not result.get('lines'):
|
|
100
|
+
self.print_debug(f"Warning: No lines found for result: {result}")
|
|
101
|
+
return None
|
|
102
|
+
lines = scanoss_scan_results_utils.get_lines(result.get('lines'))
|
|
103
|
+
if len(lines) == 0:
|
|
104
|
+
self.print_debug(f"Warning: empty lines for result: {result}")
|
|
105
|
+
return None
|
|
106
|
+
end_line = lines[len(lines) - 1] if len(lines) > 1 else lines[0]
|
|
107
|
+
description = f"Snippet found in: {file_name} - lines {lines[0]}-{end_line}"
|
|
108
|
+
return CodeQuality(
|
|
109
|
+
description=description,
|
|
110
|
+
check_name=file_name,
|
|
111
|
+
fingerprint=result.get('file_hash'),
|
|
112
|
+
severity="info",
|
|
113
|
+
location=Location(
|
|
114
|
+
path=file_name,
|
|
115
|
+
lines=Lines(
|
|
116
|
+
begin=lines[0]
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def _write_output(self, data: list[CodeQuality], output_file: str = None) -> bool:
|
|
122
|
+
"""Write the Gitlab Code Quality Report to output."""
|
|
123
|
+
try:
|
|
124
|
+
json_data = [item.to_dict() for item in data]
|
|
125
|
+
file = open(output_file, 'w') if output_file else sys.stdout
|
|
126
|
+
print(json.dumps(json_data, indent=2), file=file)
|
|
127
|
+
if output_file:
|
|
128
|
+
file.close()
|
|
129
|
+
return True
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self.print_stderr(f'Error writing output: {str(e)}')
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
def _produce_from_json(self, data: dict, output_file: str = None) -> bool:
|
|
135
|
+
code_quality = []
|
|
136
|
+
for file_name, results in data.items():
|
|
137
|
+
for result in results:
|
|
138
|
+
if not result.get('id'):
|
|
139
|
+
self.print_debug(f"Warning: No ID found for result: {result}")
|
|
140
|
+
continue
|
|
141
|
+
if result.get('id') != 'snippet' and result.get('id') != 'file':
|
|
142
|
+
self.print_debug(f"Skipping non-snippet/file match: {result}")
|
|
143
|
+
continue
|
|
144
|
+
code_quality_item = self._get_code_quality(file_name, result)
|
|
145
|
+
if code_quality_item:
|
|
146
|
+
code_quality.append(code_quality_item)
|
|
147
|
+
else:
|
|
148
|
+
self.print_debug(f"Warning: No Code Quality found for result: {result}")
|
|
149
|
+
self._write_output(data=code_quality,output_file=output_file)
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
def _produce_from_str(self, json_str: str, output_file: str = None) -> bool:
|
|
153
|
+
"""
|
|
154
|
+
Produce Gitlab Code Quality Report output from input JSON string
|
|
155
|
+
:param json_str: input JSON string
|
|
156
|
+
:param output_file: Output file (optional)
|
|
157
|
+
:return: True if successful, False otherwise
|
|
158
|
+
"""
|
|
159
|
+
if not json_str:
|
|
160
|
+
self.print_stderr('ERROR: No JSON string provided to parse.')
|
|
161
|
+
return False
|
|
162
|
+
try:
|
|
163
|
+
data = json.loads(json_str)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
166
|
+
return False
|
|
167
|
+
return self._produce_from_json(data, output_file)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def produce_from_file(self, json_file: str, output_file: str = None) -> bool:
|
|
171
|
+
"""
|
|
172
|
+
Parse plain/raw input JSON file and produce GitLab Code Quality JSON output
|
|
173
|
+
:param json_file:
|
|
174
|
+
:param output_file:
|
|
175
|
+
:return: True if successful, False otherwise
|
|
176
|
+
"""
|
|
177
|
+
if not json_file:
|
|
178
|
+
self.print_stderr('ERROR: No JSON file provided to parse.')
|
|
179
|
+
return False
|
|
180
|
+
if not os.path.isfile(json_file):
|
|
181
|
+
self.print_stderr(f'ERROR: JSON file does not exist or is not a file: {json_file}')
|
|
182
|
+
return False
|
|
183
|
+
with open(json_file, 'r') as f:
|
|
184
|
+
success = self._produce_from_str(f.read(), output_file)
|
|
185
|
+
return success
|
|
@@ -28,6 +28,7 @@ from typing import Any, Dict, List, Optional, TypedDict
|
|
|
28
28
|
|
|
29
29
|
from ...services.dependency_track_service import DependencyTrackService
|
|
30
30
|
from ..policy_check import PolicyCheck, PolicyStatus
|
|
31
|
+
from ..utils.markdown_utils import generate_jira_table, generate_table
|
|
31
32
|
|
|
32
33
|
# Constants
|
|
33
34
|
PROCESSING_RETRY_DELAY = 5 # seconds
|
|
@@ -195,7 +196,7 @@ class DependencyTrackProjectViolationPolicyCheck(PolicyCheck[PolicyViolationDict
|
|
|
195
196
|
Returns:
|
|
196
197
|
Dictionary with formatted Markdown details and summary
|
|
197
198
|
"""
|
|
198
|
-
return self._md_summary_generator(project_violations,
|
|
199
|
+
return self._md_summary_generator(project_violations, generate_table)
|
|
199
200
|
|
|
200
201
|
def _jira_markdown(self, data: list[PolicyViolationDict]) -> Dict[str, Any]:
|
|
201
202
|
"""
|
|
@@ -207,7 +208,7 @@ class DependencyTrackProjectViolationPolicyCheck(PolicyCheck[PolicyViolationDict
|
|
|
207
208
|
Returns:
|
|
208
209
|
Dictionary containing Jira markdown formatted results and summary
|
|
209
210
|
"""
|
|
210
|
-
return self._md_summary_generator(data,
|
|
211
|
+
return self._md_summary_generator(data, generate_jira_table)
|
|
211
212
|
|
|
212
213
|
def is_project_updated(self, dt_project: Dict[str, Any]) -> bool:
|
|
213
214
|
"""
|
|
@@ -137,48 +137,6 @@ class PolicyCheck(ScanossBase, Generic[T]):
|
|
|
137
137
|
"""
|
|
138
138
|
pass
|
|
139
139
|
|
|
140
|
-
def generate_table(self, headers, rows, centered_columns=None):
|
|
141
|
-
"""
|
|
142
|
-
Generate a Markdown table.
|
|
143
|
-
|
|
144
|
-
:param headers: List of headers for the table.
|
|
145
|
-
:param rows: List of rows for the table.
|
|
146
|
-
:param centered_columns: List of column indices to be centered.
|
|
147
|
-
:return: A string representing the Markdown table.
|
|
148
|
-
"""
|
|
149
|
-
col_sep = ' | '
|
|
150
|
-
centered_column_set = set(centered_columns or [])
|
|
151
|
-
if headers is None:
|
|
152
|
-
self.print_stderr('ERROR: Header are no set')
|
|
153
|
-
return None
|
|
154
|
-
|
|
155
|
-
# Decide which separator to use
|
|
156
|
-
def create_separator(index):
|
|
157
|
-
if centered_columns is None:
|
|
158
|
-
return '-'
|
|
159
|
-
return ':-:' if index in centered_column_set else '-'
|
|
160
|
-
|
|
161
|
-
# Build the row separator
|
|
162
|
-
row_separator = col_sep + col_sep.join(create_separator(index) for index, _ in enumerate(headers)) + col_sep
|
|
163
|
-
# build table rows
|
|
164
|
-
table_rows = [col_sep + col_sep.join(headers) + col_sep, row_separator]
|
|
165
|
-
table_rows.extend(col_sep + col_sep.join(row) + col_sep for row in rows)
|
|
166
|
-
return '\n'.join(table_rows)
|
|
167
|
-
|
|
168
|
-
def generate_jira_table(self, headers, rows, centered_columns=None):
|
|
169
|
-
col_sep = '*|*'
|
|
170
|
-
if headers is None:
|
|
171
|
-
self.print_stderr('ERROR: Header are no set')
|
|
172
|
-
return None
|
|
173
|
-
|
|
174
|
-
table_header = '|*' + col_sep.join(headers) + '*|\n'
|
|
175
|
-
table = table_header
|
|
176
|
-
for row in rows:
|
|
177
|
-
if len(headers) == len(row):
|
|
178
|
-
table += '|' + '|'.join(row) + '|\n'
|
|
179
|
-
|
|
180
|
-
return table
|
|
181
|
-
|
|
182
140
|
def _get_formatter(self) -> Callable[[List[dict]], Dict[str, Any]] or None:
|
|
183
141
|
"""
|
|
184
142
|
Get the appropriate formatter function based on the specified format.
|
|
File without changes
|
|
@@ -21,13 +21,58 @@ SPDX-License-Identifier: MIT
|
|
|
21
21
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
-
|
|
25
24
|
import json
|
|
25
|
+
from typing import Any
|
|
26
26
|
|
|
27
|
+
from ..policy_check import T
|
|
27
28
|
from .raw_base import RawBase
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class ComponentSummary(RawBase):
|
|
32
|
+
|
|
33
|
+
def _json(self, data: dict[str,Any]) -> dict[str,Any]:
|
|
34
|
+
"""
|
|
35
|
+
Format component summary data as JSON.
|
|
36
|
+
|
|
37
|
+
This method returns the component summary data in its original JSON structure
|
|
38
|
+
without any transformation. The data can be directly serialized to JSON format.
|
|
39
|
+
|
|
40
|
+
:param data: Dictionary containing component summary information including:
|
|
41
|
+
- components: List of component-license pairs with status and metadata
|
|
42
|
+
- totalComponents: Total number of unique components
|
|
43
|
+
- undeclaredComponents: Number of components with 'pending' status
|
|
44
|
+
- declaredComponents: Number of components with 'identified' status
|
|
45
|
+
- totalFilesDetected: Total count of files where components were detected
|
|
46
|
+
- totalFilesUndeclared: Count of files with undeclared components
|
|
47
|
+
- totalFilesDeclared: Count of files with declared components
|
|
48
|
+
:return: The same data dictionary, ready for JSON serialization
|
|
49
|
+
"""
|
|
50
|
+
return data
|
|
51
|
+
|
|
52
|
+
def _markdown(self, data: list[T]) -> dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Format component summary data as Markdown (not yet implemented).
|
|
55
|
+
|
|
56
|
+
This method is intended to convert component summary data into a human-readable
|
|
57
|
+
Markdown format with tables and formatted sections.
|
|
58
|
+
|
|
59
|
+
:param data: List of component summary items to format
|
|
60
|
+
:return: Dictionary containing formatted Markdown output
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def _jira_markdown(self, data: list[T]) -> dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Format component summary data as Jira-flavored Markdown (not yet implemented).
|
|
67
|
+
|
|
68
|
+
This method is intended to convert component summary data into Jira-compatible
|
|
69
|
+
Markdown format, which may include Jira-specific syntax for tables and formatting.
|
|
70
|
+
|
|
71
|
+
:param data: List of component summary items to format
|
|
72
|
+
:return: Dictionary containing Jira-formatted Markdown output
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
31
76
|
def _get_component_summary_from_components(self, scan_components: list)-> dict:
|
|
32
77
|
"""
|
|
33
78
|
Get a component summary from detected components.
|
|
@@ -84,10 +129,16 @@ class ComponentSummary(RawBase):
|
|
|
84
129
|
self._get_components_data(self.results, components)
|
|
85
130
|
return self._convert_components_to_list(components)
|
|
86
131
|
|
|
132
|
+
def _format(self, component_summary) -> str:
|
|
133
|
+
# TODO: Implement formatter to support dynamic outputs
|
|
134
|
+
json_data = self._json(component_summary)
|
|
135
|
+
return json.dumps(json_data, indent=2)
|
|
136
|
+
|
|
87
137
|
def run(self):
|
|
88
138
|
components = self._get_components()
|
|
89
139
|
component_summary = self._get_component_summary_from_components(components)
|
|
90
|
-
|
|
140
|
+
output = self._format(component_summary)
|
|
141
|
+
self.print_to_file_or_stdout(output, self.output)
|
|
91
142
|
return component_summary
|
|
92
143
|
#
|
|
93
144
|
# End of ComponentSummary Class
|
|
@@ -27,6 +27,7 @@ from dataclasses import dataclass
|
|
|
27
27
|
from typing import Any, Dict, List
|
|
28
28
|
|
|
29
29
|
from ..policy_check import PolicyStatus
|
|
30
|
+
from ..utils.markdown_utils import generate_jira_table, generate_table
|
|
30
31
|
from .raw_base import RawBase
|
|
31
32
|
|
|
32
33
|
|
|
@@ -111,7 +112,7 @@ class Copyleft(RawBase[Component]):
|
|
|
111
112
|
:param components: List of components with copyleft licenses
|
|
112
113
|
:return: Dictionary with formatted Markdown details and summary
|
|
113
114
|
"""
|
|
114
|
-
return self._md_summary_generator(components,
|
|
115
|
+
return self._md_summary_generator(components, generate_table)
|
|
115
116
|
|
|
116
117
|
def _jira_markdown(self, components: list[Component]) -> Dict[str, Any]:
|
|
117
118
|
"""
|
|
@@ -120,7 +121,7 @@ class Copyleft(RawBase[Component]):
|
|
|
120
121
|
:param components: List of components with copyleft licenses
|
|
121
122
|
:return: Dictionary with formatted Markdown details and summary
|
|
122
123
|
"""
|
|
123
|
-
return self._md_summary_generator(components,
|
|
124
|
+
return self._md_summary_generator(components, generate_jira_table)
|
|
124
125
|
|
|
125
126
|
def _md_summary_generator(self, components: list[Component], table_generator):
|
|
126
127
|
"""
|
|
@@ -23,7 +23,9 @@ SPDX-License-Identifier: MIT
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import json
|
|
26
|
+
from typing import Any
|
|
26
27
|
|
|
28
|
+
from ..policy_check import T
|
|
27
29
|
from .raw_base import RawBase
|
|
28
30
|
|
|
29
31
|
|
|
@@ -36,6 +38,46 @@ class LicenseSummary(RawBase):
|
|
|
36
38
|
information, providing detailed summaries including copyleft analysis and license statistics.
|
|
37
39
|
"""
|
|
38
40
|
|
|
41
|
+
def _json(self, data: dict[str,Any]) -> dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Format license summary data as JSON.
|
|
44
|
+
|
|
45
|
+
This method is intended to return the license summary data in JSON structure
|
|
46
|
+
for serialization. The data should include license information with copyleft
|
|
47
|
+
analysis and license statistics.
|
|
48
|
+
|
|
49
|
+
:param data: List of license summary items to format
|
|
50
|
+
:return: Dictionary containing license summary information including:
|
|
51
|
+
- licenses: List of detected licenses with SPDX IDs, URLs, and copyleft status
|
|
52
|
+
- detectedLicenses: Total number of unique licenses
|
|
53
|
+
- detectedLicensesWithCopyleft: Count of licenses marked as copyleft
|
|
54
|
+
"""
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
def _markdown(self, data: list[T]) -> dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Format license summary data as Markdown (not yet implemented).
|
|
60
|
+
|
|
61
|
+
This method is intended to convert license summary data into a human-readable
|
|
62
|
+
Markdown format with tables and formatted sections.
|
|
63
|
+
|
|
64
|
+
:param data: List of license summary items to format
|
|
65
|
+
:return: Dictionary containing formatted Markdown output
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def _jira_markdown(self, data: list[T]) -> dict[str, Any]:
|
|
70
|
+
"""
|
|
71
|
+
Format license summary data as Jira-flavored Markdown (not yet implemented).
|
|
72
|
+
|
|
73
|
+
This method is intended to convert license summary data into Jira-compatible
|
|
74
|
+
Markdown format, which may include Jira-specific syntax for tables and formatting.
|
|
75
|
+
|
|
76
|
+
:param data: List of license summary items to format
|
|
77
|
+
:return: Dictionary containing Jira-formatted Markdown output
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
39
81
|
# Define required license fields as class constants
|
|
40
82
|
REQUIRED_LICENSE_FIELDS = ['spdxid', 'url', 'copyleft', 'source']
|
|
41
83
|
|
|
@@ -131,10 +173,16 @@ class LicenseSummary(RawBase):
|
|
|
131
173
|
self._get_dependencies_data(self.results, components)
|
|
132
174
|
return self._convert_components_to_list(components)
|
|
133
175
|
|
|
176
|
+
def _format(self, license_summary) -> str:
|
|
177
|
+
# TODO: Implement formatter to support dynamic outputs
|
|
178
|
+
json_data = self._json(license_summary)
|
|
179
|
+
return json.dumps(json_data, indent=2)
|
|
180
|
+
|
|
134
181
|
def run(self):
|
|
135
182
|
components = self._get_components()
|
|
136
183
|
license_summary = self._get_licenses_summary_from_components(components)
|
|
137
|
-
|
|
184
|
+
output = self._format(license_summary)
|
|
185
|
+
self.print_to_file_or_stdout(output, self.output)
|
|
138
186
|
return license_summary
|
|
139
187
|
#
|
|
140
188
|
# End of LicenseSummary Class
|