Skip to content

Commit 6c55a5d

Browse files
authored
Chore/groh/add markdown jira md output
* chore:SP-2022 Adds Jira Markdown output on inspect command * chore:SP-2023 Adds Jira Markdown output tests * chore:SP-2024 Add Jira Markdown output documentation * Upgrades app version to v1.19.3
1 parent 31ecd0d commit 6c55a5d

File tree

8 files changed

+145
-7
lines changed

8 files changed

+145
-7
lines changed

Diff for: CHANGELOG.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Upcoming changes...
1111

12+
## [1.19.3] - 2025-01-07
13+
### Added
14+
- Add Jira Markdown output on inspect command ç
15+
- This is useful for calls from integrations (i.e. Jenkins)
16+
1217
## [1.19.2] - 2025-01-06
1318
### Added
1419
- Add second container image `scanoss-py-base` with no `ENTRYPOINT`
@@ -429,4 +434,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
429434
[1.18.1]: https://github.com/scanoss/scanoss.py/compare/v1.18.0...v1.18.1
430435
[1.19.0]: https://github.com/scanoss/scanoss.py/compare/v1.18.1...v1.19.0
431436
[1.19.1]: https://github.com/scanoss/scanoss.py/compare/v1.19.0...v1.19.1
432-
[1.19.2]: https://github.com/scanoss/scanoss.py/compare/v1.19.1...v1.19.2
437+
[1.19.2]: https://github.com/scanoss/scanoss.py/compare/v1.19.1...v1.19.2
438+
[1.19.3]: https://github.com/scanoss/scanoss.py/compare/v1.19.2...v1.19.3

Diff for: CLIENT_HELP.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,18 @@ scanoss-py insp undeclared -i scan-results.json --status undeclared-status.md --
378378

379379
#### Inspect for undeclared components and save results in Markdown format and show status output as sbom.json (legacy)
380380
The following command can be used to inspect for undeclared components and save the results in Markdown format.
381-
Default sbom-format 'settings'
382381
```bash
383382
scanoss-py insp undeclared -i scan-results.json --status undeclared-status.md --output undeclared.json --format md --sbom-format legacy
383+
```
384+
385+
#### Inspect for undeclared components and save results in Jira Markdown format.
386+
The following command can be used to inspect for undeclared components and save the results in Jira Markdown format.
387+
```bash
388+
scanoss-py insp undeclared -i scan-results.json --output undeclared-summary.jiramd --status undeclared-status.jiramd --format jira_md
389+
```
390+
391+
#### Inspect for copyleft licenses and save results in Jira Markdown format.
392+
The following command can be used to inspect for undeclared components and save the results in Jira Markdown format.
393+
```bash
394+
scanoss-py insp copyleft -i scan-results.json --output copyleft-summary.jiramd --status copyleft-status.jiramd --format jira_md
384395
```

Diff for: src/scanoss/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
THE SOFTWARE.
2323
"""
2424

25-
__version__ = "1.19.2"
25+
__version__ = "1.19.3"

Diff for: src/scanoss/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def setup_args() -> None:
334334

335335
for p in [p_copyleft, p_undeclared]:
336336
p.add_argument('-i', '--input', nargs='?', help='Path to results file')
337-
p.add_argument('-f', '--format',required=False ,choices=['json', 'md'], default='json', help='Output format (default: json)')
337+
p.add_argument('-f', '--format',required=False ,choices=['json', 'md', 'jira_md'], default='json', help='Output format (default: json)')
338338
p.add_argument('-o', '--output', type=str, help='Save details into a file')
339339
p.add_argument('-s', '--status', type=str, help='Save summary data into Markdown file')
340340

Diff for: src/scanoss/inspection/copyleft.py

+28
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False,
5858
self.exclude = exclude
5959
self.explicit = explicit
6060

61+
6162
def _json(self, components: list) -> Dict[str, Any]:
6263
"""
6364
Format the components with copyleft licenses as JSON.
@@ -100,6 +101,33 @@ def _markdown(self, components: list) -> Dict[str,Any]:
100101
'summary' : f'{len(components)} component(s) with copyleft licenses were found.\n'
101102
}
102103

104+
def _jira_markdown(self, components: list) -> Dict[str,Any]:
105+
"""
106+
Format the components with copyleft licenses as Markdown.
107+
108+
:param components: List of components with copyleft licenses
109+
:return: Dictionary with formatted Markdown details and summary
110+
"""
111+
headers = ['Component', 'Version', 'License', 'URL', 'Copyleft']
112+
centered_columns = [1, 4]
113+
rows: [[]]= []
114+
for component in components:
115+
for lic in component['licenses']:
116+
row = [
117+
component['purl'],
118+
component['version'],
119+
lic['spdxid'],
120+
lic['url'],
121+
'YES' if lic['copyleft'] else 'NO'
122+
]
123+
rows.append(row)
124+
# End license loop
125+
# End component loop
126+
return {
127+
'details': f'{self.generate_jira_table(headers,rows,centered_columns)}',
128+
'summary' : f'{len(components)} component(s) with copyleft licenses were found.\n'
129+
}
130+
103131
def _filter_components_with_copyleft_licenses(self, components: list) -> list:
104132
"""
105133
Filter the components list to include only those with copyleft licenses.

Diff for: src/scanoss/inspection/policy_check.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class PolicyCheck(ScanossBase):
7676
ScanossBase: A base class providing common functionality for SCANOSS-related operations.
7777
"""
7878

79-
VALID_FORMATS = {'md', 'json'}
79+
VALID_FORMATS = {'md', 'json', 'jira_md'}
8080

8181
def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False, filepath: str = None,
8282
format_type: str = None, status: str = None, output: str = None, name: str = None):
@@ -134,6 +134,19 @@ def _markdown(self, components: list) -> Dict[str, Any]:
134134
"""
135135
pass
136136

137+
@abstractmethod
138+
def _jira_markdown(self, components: list) -> Dict[str, Any]:
139+
"""
140+
Generate Markdown output for the policy check results.
141+
142+
This method should be implemented by subclasses to create a Markdown representation
143+
of the policy check results.
144+
145+
:param components: List of components to be included in the output.
146+
:return: A dictionary representing the Markdown output.
147+
"""
148+
pass
149+
137150
def _append_component(self,components: Dict[str, Any], new_component: Dict[str, Any],
138151
id: str, status: str) -> Dict[str, Any]:
139152
"""
@@ -270,6 +283,20 @@ def create_separator(index):
270283
table_rows.extend(col_sep + col_sep.join(row) + col_sep for row in rows)
271284
return '\n'.join(table_rows)
272285

286+
def generate_jira_table(self, headers, rows, centered_columns=None):
287+
col_sep = '*|*'
288+
if headers is None:
289+
self.print_stderr('ERROR: Header are no set')
290+
return None
291+
292+
table_header = '|*' + col_sep.join(headers) + '*|\\n'
293+
table = table_header
294+
for row in rows:
295+
if len(headers) == len(row):
296+
table += '|' + '|'.join(row) + '|\\n'
297+
298+
return table
299+
273300
def _get_formatter(self)-> Callable[[List[dict]], Dict[str,Any]] or None:
274301
"""
275302
Get the appropriate formatter function based on the specified format.
@@ -282,7 +309,8 @@ def _get_formatter(self)-> Callable[[List[dict]], Dict[str,Any]] or None:
282309
# a map of which format function to return
283310
function_map = {
284311
'json': self._json,
285-
'md': self._markdown
312+
'md': self._markdown,
313+
'jira_md': self._jira_markdown,
286314
}
287315
return function_map[self.format_type]
288316

Diff for: src/scanoss/inspection/undeclared_component.py

+38
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ def _get_undeclared_component(self, components: list)-> list or None:
7171
# end component loop
7272
return undeclared_components
7373

74+
def _get_jira_summary(self, components: list) -> str:
75+
"""
76+
Get a summary of the undeclared components.
77+
78+
:param components: List of all components
79+
:return: Component summary markdown
80+
"""
81+
if len(components) > 0:
82+
if self.sbom_format == 'settings':
83+
json_str = (json.dumps(self._generate_scanoss_file(components), indent=2).replace('\n', '\\n')
84+
.replace('"', '\\"'))
85+
return f'{len(components)} undeclared component(s) were found.\\nAdd the following snippet into your `scanoss.json` file\\n{{code:json}}\\n{json_str}\\n{{code}}\\n'
86+
else:
87+
json_str = (json.dumps(self._generate_scanoss_file(components), indent=2).replace('\n', '\\n')
88+
.replace('"', '\\"'))
89+
return f'{len(components)} undeclared component(s) were found.\\nAdd the following snippet into your `sbom.json` file\\n{{code:json}}\\n{json_str}\\n{{code}}\\n'
90+
91+
return f'{len(components)} undeclared component(s) were found.\\n'
92+
93+
7494
def _get_summary(self, components: list) -> str:
7595
"""
7696
Get a summary of the undeclared components.
@@ -122,6 +142,24 @@ def _markdown(self, components: list) -> Dict[str,Any]:
122142
'summary': self._get_summary(components),
123143
}
124144

145+
def _jira_markdown(self, components: list) -> Dict[str,Any]:
146+
"""
147+
Format the undeclared components as Markdown.
148+
149+
:param components: List of undeclared components
150+
:return: Dictionary with formatted Markdown details and summary
151+
"""
152+
headers = ['Component', 'Version', 'License']
153+
rows: [[]]= []
154+
# TODO look at using SpdxLite license name lookup method
155+
for component in components:
156+
licenses = " - ".join(lic.get('spdxid', 'Unknown') for lic in component['licenses'])
157+
rows.append([component['purl'], component['version'], licenses])
158+
return {
159+
'details': f'{self.generate_jira_table(headers,rows)}',
160+
'summary': self._get_jira_summary(components),
161+
}
162+
125163
def _get_unique_components(self, components: list) -> list:
126164
"""
127165
Generate a list of unique components.

Diff for: tests/test_policy_inspect.py

+28-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def test_copyleft_policy_markdown(self):
153153
Inspect for undeclared components empty path
154154
"""
155155
def test_copyleft_policy_empty_path(self):
156-
copyleft = UndeclaredComponent(filepath='', format_type='json')
156+
copyleft = Copyleft(filepath='', format_type='json')
157157
success, results = copyleft.run()
158158
self.assertTrue(success,2)
159159

@@ -332,5 +332,32 @@ def test_undeclared_policy_scanoss_summary(self):
332332
self.assertEqual(re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary),
333333
re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output))
334334

335+
def test_undeclared_policy_jira_markdown_output(self):
336+
script_dir = os.path.dirname(os.path.abspath(__file__))
337+
file_name = "result.json"
338+
input_file_name = os.path.join(script_dir, 'data', file_name)
339+
undeclared = UndeclaredComponent(filepath=input_file_name, format_type='jira_md')
340+
status, results = undeclared.run()
341+
details = results['details']
342+
summary = results['summary']
343+
expected_details_output = "|*Component*|*Version*|*License*|\\n|pkg:github/scanoss/scanner.c|1.3.3|BSD-2-Clause - GPL-2.0-only|\\n|pkg:github/scanoss/scanner.c|1.1.4|GPL-2.0-only|\\n|pkg:github/scanoss/wfp|6afc1f6|Zlib - GPL-2.0-only|\\n|pkg:npm/%40electron/rebuild|3.7.0|MIT|\\n|pkg:npm/%40emotion/react|11.13.3|MIT|\\n"
344+
expected_summary_output = r"5 undeclared component(s) were found.\nAdd the following snippet into your `scanoss.json` file\n{code:json}\n{\n \"bom\": {\n \"include\": [\n {\n \"purl\": \"pkg:github/scanoss/scanner.c\"\n },\n {\n \"purl\": \"pkg:github/scanoss/wfp\"\n },\n {\n \"purl\": \"pkg:npm/%40electron/rebuild\"\n },\n {\n \"purl\": \"pkg:npm/%40emotion/react\"\n }\n ]\n }\n}\n{code}\n"
345+
self.assertEqual(status, 0)
346+
self.assertEqual(expected_details_output, details)
347+
self.assertEqual(summary, expected_summary_output)
348+
349+
def test_copyleft_policy_jira_markdown_output(self):
350+
script_dir = os.path.dirname(os.path.abspath(__file__))
351+
file_name = "result.json"
352+
input_file_name = os.path.join(script_dir, 'data', file_name)
353+
copyleft = Copyleft(filepath=input_file_name, format_type='jira_md')
354+
status, results = copyleft.run()
355+
details = results['details']
356+
expected_details_output = r"|*Component*|*Version*|*License*|*URL*|*Copyleft*|\n|pkg:github/scanoss/scanner.c|1.3.3|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|\n|pkg:github/scanoss/scanner.c|1.1.4|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|\n|pkg:github/scanoss/engine|5.4.0|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|\n|pkg:github/scanoss/wfp|6afc1f6|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|\n|pkg:github/scanoss/engine|4.0.4|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|\n"
357+
self.assertEqual(status, 0)
358+
self.assertEqual(expected_details_output, details)
359+
360+
361+
335362
if __name__ == '__main__':
336363
unittest.main()

0 commit comments

Comments
 (0)