From 45c868e2990d2d4fbdb7a61c03dae8491ff1493d Mon Sep 17 00:00:00 2001 From: Brook Elgie Date: Thu, 1 Sep 2022 11:55:33 +0100 Subject: [PATCH 1/2] Handle parsing of reports with no .Results list A trivy fs scan may have no .Results list if there are no manifests for it to process. This might happen with an empty directory, for example. This commit allows the report_generator.parse_results function to return None if there is no Results key in the report data. print_issue will test that the value returned by parse_results is not None before proceeding. Fixes #1. --- tests/scans/readme.md | 3 ++- tests/scans/scan4.json | 17 +++++++++++++++++ tests/test_parse_report.py | 7 +++++++ trivy_report/print_issues.py | 9 +++++---- trivy_report/report_generator.py | 9 +++++++-- 5 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 tests/scans/scan4.json diff --git a/tests/scans/readme.md b/tests/scans/readme.md index 679d370..d8fc4d8 100644 --- a/tests/scans/readme.md +++ b/tests/scans/readme.md @@ -2,4 +2,5 @@ - `scan1.json` - Example with vulnerabilities for 3 packages. - `scan2.json` - Example with single vulnerability for one package. -- `scan2.json` - Example with multiple vulnerabilities for one package. +- `scan3.json` - Example with multiple vulnerabilities for one package. +- `scan4.json` - Example with nothing for trivy to scan. diff --git a/tests/scans/scan4.json b/tests/scans/scan4.json new file mode 100644 index 0000000..f376f5c --- /dev/null +++ b/tests/scans/scan4.json @@ -0,0 +1,17 @@ +{ + "SchemaVersion": 2, + "ArtifactName": ".", + "ArtifactType": "filesystem", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + } +} diff --git a/tests/test_parse_report.py b/tests/test_parse_report.py index fa2f743..4dc723d 100644 --- a/tests/test_parse_report.py +++ b/tests/test_parse_report.py @@ -27,6 +27,13 @@ def test_parse_report(report_filename: str, report_count: int): for report in reports: assert isinstance(report, Report) +def test_parse_report_no_results(): + # Test that it is able to parse Trivy reports with no Results list + data: ReportDict = json.load(open("tests/scans/scan4.json", "rb")) + assert isinstance(data, dict) + + parsed_result = parse_results(data, existing_issues=[]) + assert parsed_result is None def test_parse_report1(): data: ReportDict = json.load(open("tests/scans/scan1.json", "rb")) diff --git a/trivy_report/print_issues.py b/trivy_report/print_issues.py index 2206dd4..0a1f280 100755 --- a/trivy_report/print_issues.py +++ b/trivy_report/print_issues.py @@ -43,11 +43,12 @@ def main(): reports = parse_results(data, existing_issues=existing_issues) except TypeError as e: abort(f"Failed to parse Trivy JSON report: {e}") - issues = generate_issues(reports) + if reports is not None: + issues = generate_issues(reports) - for issue in issues: - print(issue.title) - print(issue.body, end="\0") + for issue in issues: + print(issue.title) + print(issue.body, end="\0") if __name__ == "__main__": diff --git a/trivy_report/report_generator.py b/trivy_report/report_generator.py index 0096e55..88710f7 100644 --- a/trivy_report/report_generator.py +++ b/trivy_report/report_generator.py @@ -76,12 +76,17 @@ class Issue: def parse_results(data: ReportDict, existing_issues: List[str]) -> Iterator[Report]: """ - Parses Trivy result structure and creates a report per package/version that was found. + Parses Trivy result structure and creates a report per package/version that + was found. Return None if no Results found, ie. nothing to parse. :param data: The report data that was parsed from JSON file. :param existing_issues: List of GitHub issues, used to exclude already reported issues. """ - results = data["Results"] + try: + results = data["Results"] + except KeyError as e: + return None + if not isinstance(results, list): raise TypeError( f"The JSON entry .Results is not a list, got: {type(results).__name__}" From 92f11f51beab2659e5d76738b1e8e3165a9b8f46 Mon Sep 17 00:00:00 2001 From: Brook Elgie Date: Fri, 2 Sep 2022 12:29:39 +0100 Subject: [PATCH 2/2] Check reports is not None before iterating issues report_generator.parse_results can return None, so handle this in report_issues. --- trivy_report/report_generator.py | 4 +-- trivy_report/report_issues.py | 59 +++++++++++++++++--------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/trivy_report/report_generator.py b/trivy_report/report_generator.py index 88710f7..2ca1c30 100644 --- a/trivy_report/report_generator.py +++ b/trivy_report/report_generator.py @@ -1,6 +1,6 @@ import collections from dataclasses import dataclass -from typing import Iterator, List, Optional, OrderedDict, TypedDict +from typing import Iterator, List, Optional, OrderedDict, Tuple, TypedDict # Types for dictionaries found in JSON data @@ -74,7 +74,7 @@ class Issue: body: str -def parse_results(data: ReportDict, existing_issues: List[str]) -> Iterator[Report]: +def parse_results(data: ReportDict, existing_issues: List[str]) -> Tuple[Iterator[Report], None]: """ Parses Trivy result structure and creates a report per package/version that was found. Return None if no Results found, ie. nothing to parse. diff --git a/trivy_report/report_issues.py b/trivy_report/report_issues.py index eab356e..4b26648 100755 --- a/trivy_report/report_issues.py +++ b/trivy_report/report_issues.py @@ -76,35 +76,40 @@ def main(): reports = parse_results(data, existing_issues=existing_issues) except TypeError as e: abort(f"Failed to parse Trivy JSON report: {e}") - issues = generate_issues(reports) - for issue in issues: - print(f"Creating GitHub issue `{issue.title}`") - print( - f'gh --repo "{github_repo}" issue create --title "{issue.title}" --body ... --label "{input_label}" ' - + " ".join(extra_args) - ) - proc = subprocess.Popen( - [ - "gh", - "--repo", - github_repo, - "issue", - "create", - "--title", - issue.title, - "--body", - issue.body, - "--label", - input_label, - ] - + extra_args - ) - proc.communicate() - if proc.returncode != 0: - abort("Failed to create issue with `gh` cli") + if reports is None: + print("No reports to create issues for") else: - print("No new vulnerabilities found") + issues = generate_issues(reports) + + for issue in issues: + print(f"Creating GitHub issue `{issue.title}`") + print( + f'gh --repo "{github_repo}" issue create --title "{issue.title}" --body ... --label "{input_label}" ' + + " ".join(extra_args) + ) + proc = subprocess.Popen( + [ + "gh", + "--repo", + github_repo, + "issue", + "create", + "--title", + issue.title, + "--body", + issue.body, + "--label", + input_label, + ] + + extra_args + ) + proc.communicate() + if proc.returncode != 0: + abort("Failed to create issue with `gh` cli") + else: + print("No new vulnerabilities found") + if __name__ == "__main__":