From 43f3a764b229fc2fdd6eb3d93fa093b2c3bf1cc6 Mon Sep 17 00:00:00 2001 From: Agustin Groh <77737320+agustingroh@users.noreply.github.com> Date: Fri, 15 Nov 2024 08:40:28 -0300 Subject: [PATCH] feat:SP-1846 Adds sbom-format flag to inspect undeclared command * feat:SP-1846 Adds sbom-format flag to inspect undeclared command --- CHANGELOG.md | 8 +- CLIENT_HELP.md | 7 ++ src/scanoss/cli.py | 4 +- .../inspection/undeclared_component.py | 52 +++++++--- .../{test_csvoutput.py => test_csv_output.py} | 0 ...licy-inspect.py => test_policy_inspect.py} | 96 ++++++++++++++++++- ...ocessor.py => test_scan_post_processor.py} | 0 7 files changed, 149 insertions(+), 18 deletions(-) rename tests/{test_csvoutput.py => test_csv_output.py} (100%) rename tests/{test_policy-inspect.py => test_policy_inspect.py} (73%) rename tests/{test_scanpostprocessor.py => test_scan_post_processor.py} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b79b469..7fe61f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed post processor being accesed if not set ### Added -- Add support for replace action when specifying a settings file -- Add replaced files as context to scan request +- Added support for replace action when specifying a settings file +- Added replaced files as context to scan request +- Added sbom format flag to define status output for undeclared policy ## [1.17.5] - 2024-11-12 ### Fixed @@ -401,4 +402,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.17.2]: https://github.com/scanoss/scanoss.py/compare/v1.17.1...v1.17.2 [1.17.3]: https://github.com/scanoss/scanoss.py/compare/v1.17.2...v1.17.3 [1.17.4]: https://github.com/scanoss/scanoss.py/compare/v1.17.3...v1.17.4 -[1.17.5]: https://github.com/scanoss/scanoss.py/compare/v1.17.4...v1.17.5 \ No newline at end of file +[1.17.5]: https://github.com/scanoss/scanoss.py/compare/v1.17.4...v1.17.5 +[1.18.0]: https://github.com/scanoss/scanoss.py/compare/v1.17.5...v1.18.0 \ No newline at end of file diff --git a/CLIENT_HELP.md b/CLIENT_HELP.md index a92212a..3913303 100644 --- a/CLIENT_HELP.md +++ b/CLIENT_HELP.md @@ -374,4 +374,11 @@ scanoss-py insp undeclared -i scan-results.json --status undeclared-status.md -- The following command can be used to inspect for undeclared components and save the results in Markdown format. ```bash scanoss-py insp undeclared -i scan-results.json --status undeclared-status.md --output undeclared.json --format md +``` + +#### Inspect for undeclared components and save results in Markdown format and show status output as sbom.json (legacy) +The following command can be used to inspect for undeclared components and save the results in Markdown format. +Default sbom-format 'settings' +```bash +scanoss-py insp undeclared -i scan-results.json --status undeclared-status.md --output undeclared.json --format md --sbom-format legacy ``` \ No newline at end of file diff --git a/src/scanoss/cli.py b/src/scanoss/cli.py index 9eadcac..8d2f7ad 100644 --- a/src/scanoss/cli.py +++ b/src/scanoss/cli.py @@ -315,6 +315,8 @@ def setup_args() -> None: # Inspect Sub-command: inspect undeclared p_undeclared = p_inspect_sub.add_parser('undeclared', aliases=['un'],description="Inspect for undeclared components", help='Inspect for undeclared components') + p_undeclared.add_argument('--sbom-format',required=False ,choices=['legacy', 'settings'], + default="settings",help='Sbom format for status output') p_undeclared.set_defaults(func=inspect_undeclared) for p in [p_copyleft, p_undeclared]: @@ -858,7 +860,7 @@ def inspect_undeclared(parser, args): open(status_output, 'w').close() i_undeclared = UndeclaredComponent(debug=args.debug, trace=args.trace, quiet=args.quiet, filepath=args.input, format_type=args.format, - status=status_output, output=output) + status=status_output, output=output, sbom_format=args.sbom_format) status, _ = i_undeclared.run() sys.exit(status) diff --git a/src/scanoss/inspection/undeclared_component.py b/src/scanoss/inspection/undeclared_component.py index f18ff14..4e618fd 100644 --- a/src/scanoss/inspection/undeclared_component.py +++ b/src/scanoss/inspection/undeclared_component.py @@ -32,7 +32,7 @@ class UndeclaredComponent(PolicyCheck): """ def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False, filepath: str = None, - format_type: str = 'json', status: str = None, output: str = None): + format_type: str = 'json', status: str = None, output: str = None, sbom_format: str = 'settings'): """ Initialize the UndeclaredComponent class. @@ -43,6 +43,7 @@ def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False, :param format_type: Output format ('json' or 'md') :param status: Path to save status output :param output: Path to save detailed output + :param sbom_format: Sbom format for status output (default 'settings') """ super().__init__(debug, trace, quiet, filepath, format_type, status, output, name='Undeclared Components Policy') @@ -50,6 +51,7 @@ def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False, self.format = format self.output = output self.status = status + self.sbom_format = sbom_format def _get_undeclared_component(self, components: list)-> list or None: """ @@ -59,7 +61,7 @@ def _get_undeclared_component(self, components: list)-> list or None: :return: List of undeclared components """ if components is None: - self.print_stderr(f'WARNING: No components provided!') + self.print_debug(f'WARNING: No components provided!') return None undeclared_components = [] for component in components: @@ -78,9 +80,14 @@ def _get_summary(self, components: list) -> str: """ summary = f'{len(components)} undeclared component(s) were found.\n' if len(components) > 0: + if self.sbom_format == 'settings': + summary += (f'Add the following snippet into your `scanoss.json` file\n' + f'\n```json\n{json.dumps(self._generate_scanoss_file(components), indent=2)}\n```\n') + return summary + summary += (f'Add the following snippet into your `sbom.json` file\n' f'\n```json\n{json.dumps(self._generate_sbom_file(components), indent=2)}\n```\n') - return summary + return summary def _json(self, components: list) -> Dict[str, Any]: """ @@ -115,23 +122,46 @@ def _markdown(self, components: list) -> Dict[str,Any]: 'summary': self._get_summary(components), } - def _generate_sbom_file(self, components: list) -> dict: + def _get_unique_components(self, components: list) -> list: """ - Generate a list of PURLs for the SBOM file. + Generate a list of unique components. :param components: List of undeclared components - :return: SBOM Dictionary with components + :return: list of unique components """ - unique_components = {} if components is None: self.print_stderr(f'WARNING: No components provided!') - else: - for component in components: - unique_components[component['purl']] = { 'purl': component['purl'] } + return [] + + for component in components: + unique_components[component['purl']] = {'purl': component['purl']} + return list(unique_components.values()) + + def _generate_scanoss_file(self, components: list) -> dict: + """ + Generate a list of PURLs for the scanoss.json file. + + :param components: List of undeclared components + :return: scanoss.json Dictionary + """ + scanoss_settings = { + 'bom':{ + 'include': self._get_unique_components(components), + } + } + return scanoss_settings + + def _generate_sbom_file(self, components: list) -> dict: + """ + Generate a list of PURLs for the SBOM file. + + :param components: List of undeclared components + :return: SBOM Dictionary with components + """ sbom = { - 'components': list(unique_components.values()) + 'components': self._get_unique_components(components), } return sbom diff --git a/tests/test_csvoutput.py b/tests/test_csv_output.py similarity index 100% rename from tests/test_csvoutput.py rename to tests/test_csv_output.py diff --git a/tests/test_policy-inspect.py b/tests/test_policy_inspect.py similarity index 73% rename from tests/test_policy-inspect.py rename to tests/test_policy_inspect.py index 7fc41d6..65b0f44 100644 --- a/tests/test_policy-inspect.py +++ b/tests/test_policy_inspect.py @@ -165,7 +165,7 @@ def test_undeclared_policy(self): script_dir = os.path.dirname(os.path.abspath(__file__)) file_name = "result.json" input_file_name = os.path.join(script_dir,'data', file_name) - undeclared = UndeclaredComponent(filepath=input_file_name, format_type='json') + undeclared = UndeclaredComponent(filepath=input_file_name, format_type='json', sbom_format='legacy') status, results = undeclared.run() details = json.loads(results['details']) summary = results['summary'] @@ -201,7 +201,7 @@ def test_undeclared_policy_markdown(self): script_dir = os.path.dirname(os.path.abspath(__file__)) file_name = "result.json" input_file_name = os.path.join(script_dir, 'data', file_name) - undeclared = UndeclaredComponent(filepath=input_file_name, format_type='md') + undeclared = UndeclaredComponent(filepath=input_file_name, format_type='md', sbom_format='legacy') status, results = undeclared.run() details = results['details'] summary = results['summary'] @@ -241,6 +241,96 @@ def test_undeclared_policy_markdown(self): '', expected_details_output)) self.assertEqual(re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)) - + + """ + Undeclared component markdown scanoss summary output + """ + def test_undeclared_policy_markdown_scanoss_summary(self): + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_name = "result.json" + input_file_name = os.path.join(script_dir, 'data', file_name) + undeclared = UndeclaredComponent(filepath=input_file_name, format_type='md') + status, results = undeclared.run() + details = results['details'] + summary = results['summary'] + expected_details_output = """ ### Undeclared components + | Component | Version | License | + | - | - | - | + | pkg:github/scanoss/scanner.c | 1.3.3 | BSD-2-Clause - GPL-2.0-only | + | pkg:github/scanoss/scanner.c | 1.1.4 | GPL-2.0-only | + | pkg:github/scanoss/wfp | 6afc1f6 | Zlib - GPL-2.0-only | + | pkg:npm/%40electron/rebuild | 3.7.0 | MIT | + | pkg:npm/%40emotion/react | 11.13.3 | MIT | """ + + expected_summary_output = """5 undeclared component(s) were found. + Add the following snippet into your `scanoss.json` file + + ```json + { + "bom": { + "include": [ + { + "purl": "pkg:github/scanoss/scanner.c" + }, + { + "purl": "pkg:github/scanoss/wfp" + }, + { + "purl": "pkg:npm/%40electron/rebuild" + }, + { + "purl": "pkg:npm/%40emotion/react" + } + ] + } + } + ```""" + + print(summary) + self.assertEqual(status, 0) + self.assertEqual(re.sub(r'\s|\\(?!`)|\\(?=`)', '', details), re.sub(r'\s|\\(?!`)|\\(?=`)', + '', expected_details_output)) + self.assertEqual(re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), + re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)) + + """ + Undeclared component sbom summary output + """ + def test_undeclared_policy_scanoss_summary(self): + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_name = "result.json" + input_file_name = os.path.join(script_dir, 'data', file_name) + undeclared = UndeclaredComponent(filepath=input_file_name) + status, results = undeclared.run() + details = json.loads(results['details']) + summary = results['summary'] + expected_summary_output = """5 undeclared component(s) were found. + Add the following snippet into your `scanoss.json` file + + ```json + { + "bom": { + "include": [ + { + "purl": "pkg:github/scanoss/scanner.c" + }, + { + "purl": "pkg:github/scanoss/wfp" + }, + { + "purl": "pkg:npm/%40electron/rebuild" + }, + { + "purl": "pkg:npm/%40emotion/react" + } + ] + } + } + ```""" + self.assertEqual(status, 0) + self.assertEqual(len(details['components']), 5) + self.assertEqual(re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), + re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/test_scanpostprocessor.py b/tests/test_scan_post_processor.py similarity index 100% rename from tests/test_scanpostprocessor.py rename to tests/test_scan_post_processor.py