We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
The junitxml output is missing the `guide` links
**Describe the bug**
The junitxml output is missing the `guide` section. The output consists of, for example:

```
	<testsuite disabled="0" errors="0" failures="0" name="CKV_AZURE_14/Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service" package="checkov.terraform.checks.resource.azure.AppServiceHTTPSOnly" skipped="0" tests="2" time="0">
		<testcase classname="checkov.terraform.checks.resource.azure.AppServiceHTTPSOnly" file="plan.json" name="None CKV_AZURE_14/Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service azurerm_app_service.appservice"/>
	</testsuite>
```
It provides needed data, but it is missing a link to the guide. The issue was found when we tried to publish results in the Azure DevOps pipeline and saw there were no links to the solution.

**To Reproduce**
Steps to reproduce the behavior:
1. docker pull bridgecrew/checkov:2
2. docker run --tty --volume dir:/dir bridgecrew/checkov -f /dir/plan.json --output junitxml > dir/report.xml

**Expected behavior**
Links to the solution should be visible in Azure pipelines published results. 

**Desktop:**
 - OS: [ubuntu-20.04]
 - Checkov Version [2.0.240]



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `checkov/common/output/report.py`
Content:
```
1 import json
2 from collections import defaultdict
3 from typing import List, Dict, Union, Any, Optional
4 
5 from colorama import init
6 from junit_xml import TestCase, TestSuite, to_xml_report_string
7 from tabulate import tabulate
8 from termcolor import colored
9 
10 from checkov.common.models.enums import CheckResult
11 from checkov.common.output.record import Record
12 from checkov.common.util.type_forcers import convert_csv_string_arg_to_list
13 from checkov.version import version
14 
15 init(autoreset=True)
16 
17 
18 class Report:
19     def __init__(self, check_type: str):
20         self.check_type: str = check_type
21         self.passed_checks: List[Record] = []
22         self.failed_checks: List[Record] = []
23         self.skipped_checks: List[Record] = []
24         self.parsing_errors: List[str] = []
25 
26     def add_parsing_errors(self, errors: List[str]) -> None:
27         for file in errors:
28             self.add_parsing_error(file)
29 
30     def add_parsing_error(self, file: str) -> None:
31         if file:
32             self.parsing_errors.append(file)
33 
34     def add_record(self, record: Record) -> None:
35         if record.check_result['result'] == CheckResult.PASSED:
36             self.passed_checks.append(record)
37         if record.check_result['result'] == CheckResult.FAILED:
38             self.failed_checks.append(record)
39         if record.check_result['result'] == CheckResult.SKIPPED:
40             self.skipped_checks.append(record)
41 
42     def get_summary(self) -> Dict[str, Union[int, str]]:
43         return {
44             "passed": len(self.passed_checks),
45             "failed": len(self.failed_checks),
46             "skipped": len(self.skipped_checks),
47             "parsing_errors": len(self.parsing_errors),
48             "resource_count": self._count_resources(),
49             "checkov_version": version
50         }
51 
52     def get_json(self) -> str:
53         return json.dumps(self.get_dict(), indent=4)
54 
55     def get_dict(self, is_quiet=False) -> dict:
56         if is_quiet:
57             return {
58                 "check_type": self.check_type,
59                 "results": {
60                     "failed_checks": [check.__dict__ for check in self.failed_checks]
61                 },
62                 "summary": self.get_summary()
63             }
64         else:
65             return {
66                 "check_type": self.check_type,
67                 "results": {
68                     "passed_checks": [check.__dict__ for check in self.passed_checks],
69                     "failed_checks": [check.__dict__ for check in self.failed_checks],
70                     "skipped_checks": [check.__dict__ for check in self.skipped_checks],
71                     "parsing_errors": list(self.parsing_errors)
72                 },
73                 "summary": self.get_summary()
74             }
75 
76     def get_exit_code(self, soft_fail: bool, soft_fail_on: Optional[list] = None,
77                       hard_fail_on: Optional[list] = None) -> int:
78         """
79         Returns the appropriate exit code depending on the flags that are passed in.
80 
81         :param soft_fail: If true, exit code is always 0. (default is false)
82         :param soft_fail_on: A list of checks that will return exit code 0 if they fail. Other failing checks will
83         result exit code 1.
84         :param hard_fail_on: A list of checks that will return exit code 1 if they fail. Other failing checks will
85         result exit code 0.
86         :return: Exit code 0 or 1.
87         """
88         if soft_fail_on:
89             soft_fail_on = convert_csv_string_arg_to_list(soft_fail_on)
90             if all(check_id in soft_fail_on for check_id in
91                    (failed_check.check_id for failed_check in self.failed_checks)):
92                 # List of "failed checks" is a subset of the "soft fail on" list.
93                 return 0
94             else:
95                 return 1
96         if hard_fail_on:
97             hard_fail_on = convert_csv_string_arg_to_list(hard_fail_on)
98             if any(check_id in hard_fail_on for check_id in
99                    (failed_check.check_id for failed_check in self.failed_checks)):
100                 # Any check from the list of "failed checks" is in the list of "hard fail on checks".
101                 return 1
102             else:
103                 return 0
104         if soft_fail:
105             return 0
106         elif len(self.failed_checks) > 0:
107             return 1
108         return 0
109 
110     def is_empty(self) -> bool:
111         return len(self.passed_checks + self.failed_checks + self.skipped_checks) + len(self.parsing_errors) == 0
112 
113     def print_console(self, is_quiet=False, is_compact=False, created_baseline_path=None, baseline=None) -> None:
114         summary = self.get_summary()
115         print(colored(f"{self.check_type} scan results:", "blue"))
116         if self.parsing_errors:
117             message = "\nPassed checks: {}, Failed checks: {}, Skipped checks: {}, Parsing errors: {}\n".format(
118                 summary["passed"], summary["failed"], summary["skipped"], summary["parsing_errors"])
119         else:
120             message = "\nPassed checks: {}, Failed checks: {}, Skipped checks: {}\n".format(
121                 summary["passed"], summary["failed"], summary["skipped"])
122         print(colored(message, "cyan"))
123         if not is_quiet:
124             for record in self.passed_checks:
125                 print(record.to_string(compact=is_compact))
126         for record in self.failed_checks:
127             print(record.to_string(compact=is_compact))
128         if not is_quiet:
129             for record in self.skipped_checks:
130                 print(record.to_string(compact=is_compact))
131 
132         if not is_quiet:
133             for file in self.parsing_errors:
134                 Report._print_parsing_error_console(file)
135 
136         if created_baseline_path:
137             print(colored(f"Created a checkov baseline file at {created_baseline_path}", "blue"))
138 
139         if baseline:
140             print(colored(
141                 f"Baseline analysis report using {baseline.path} - only new failed checks with respect to the baseline are reported",
142                 "blue"))
143 
144 
145     @staticmethod
146     def _print_parsing_error_console(file: str) -> None:
147         print(colored(f'Error parsing file {file}', 'red'))
148 
149     def print_junit_xml(self) -> None:
150         ts = self.get_test_suites()
151         xml_string = self.get_junit_xml_string(ts)
152         print(xml_string)
153 
154     @staticmethod
155     def get_junit_xml_string(ts: List[TestSuite]) -> str:
156         return to_xml_report_string(ts)
157 
158     def print_failed_github_md(self) -> None:
159         result = []
160         for record in self.failed_checks:
161             result.append([record.check_id, record.file_path, record.resource, record.check_name, record.guideline])
162         print(tabulate(result, headers=["check_id", "file", "resource", "check_name", "guideline"], tablefmt="github",
163                        showindex=True))
164         print("\n\n---\n\n")
165 
166     def get_test_suites(self) -> List[TestSuite]:
167         test_cases = defaultdict(list)
168         test_suites = []
169         records = self.passed_checks + self.failed_checks + self.skipped_checks
170         for record in records:
171             check_name = f'{record.check_id}/{record.check_name}'
172 
173             test_name = f'{self.check_type} {check_name} {record.resource}'
174             test_case = TestCase(name=test_name, file=record.file_path, classname=record.check_class)
175             if record.check_result['result'] == CheckResult.FAILED:
176                 if record.file_path and record.file_line_range:
177                     test_case.add_failure_info(
178                         f'Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range}')
179                 else:
180                     test_case.add_failure_info(
181                         f'Resource {record.resource} failed in check {check_name}')
182             if record.check_result['result'] == CheckResult.SKIPPED:
183                 test_case.add_skipped_info(
184                     f'Resource {record.resource} skipped in check {check_name} \n Suppress comment: {record.check_result["suppress_comment"]}')
185 
186             test_cases[check_name].append(test_case)
187         for key in test_cases.keys():
188             test_suites.append(
189                 TestSuite(name=key, test_cases=test_cases[key], package=test_cases[key][0].classname))
190         return test_suites
191 
192     def print_json(self) -> None:
193         print(self.get_json())
194 
195     def _count_resources(self) -> int:
196         unique_resources = set()
197         for record in self.passed_checks + self.failed_checks:
198             unique_resources.add(record.file_path + '.' + record.resource)
199         return len(unique_resources)
200 
201     @staticmethod
202     def enrich_plan_report(report: "Report", enriched_resources: Dict[str, Dict[str, Any]]) -> "Report":
203         # This enriches reports with the appropriate filepath, line numbers, and codeblock
204         for record in report.failed_checks:
205             enriched_resource = enriched_resources.get(record.resource)
206             if enriched_resource:
207                 record.file_path = enriched_resource["scanned_file"]
208                 record.file_line_range = enriched_resource["entity_lines_range"]
209                 record.code_block = enriched_resource["entity_code_lines"]
210         return report
211 
212     @staticmethod
213     def handle_skipped_checks(report: "Report", enriched_resources: Dict[str, Dict[str, Any]]) -> "Report":
214         for record in report.failed_checks:
215             resource_skips = enriched_resources.get(record.resource, {}).get("skipped_checks", [])
216             for skip in resource_skips:
217                 if record.check_id in skip["id"]:
218                     # Remove and re-add the record to make Checkov mark it as skipped
219                     report.failed_checks.remove(record)
220                     record.check_result["result"] = CheckResult.SKIPPED
221                     record.check_result["suppress_comment"] = skip["suppress_comment"]
222                     report.add_record(record)
223         return report
224
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/checkov/common/output/report.py b/checkov/common/output/report.py
--- a/checkov/common/output/report.py
+++ b/checkov/common/output/report.py
@@ -175,13 +175,13 @@
             if record.check_result['result'] == CheckResult.FAILED:
                 if record.file_path and record.file_line_range:
                     test_case.add_failure_info(
-                        f'Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range}')
+                        f'Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range} - Guideline: {record.guideline}')
                 else:
                     test_case.add_failure_info(
                         f'Resource {record.resource} failed in check {check_name}')
             if record.check_result['result'] == CheckResult.SKIPPED:
                 test_case.add_skipped_info(
-                    f'Resource {record.resource} skipped in check {check_name} \n Suppress comment: {record.check_result["suppress_comment"]}')
+                    f'Resource {record.resource} skipped in check {check_name} \n Suppress comment: {record.check_result["suppress_comment"]} - Guideline: {record.guideline}')
 
             test_cases[check_name].append(test_case)
         for key in test_cases.keys():

{"golden_diff": "diff --git a/checkov/common/output/report.py b/checkov/common/output/report.py\n--- a/checkov/common/output/report.py\n+++ b/checkov/common/output/report.py\n@@ -175,13 +175,13 @@\n             if record.check_result['result'] == CheckResult.FAILED:\n                 if record.file_path and record.file_line_range:\n                     test_case.add_failure_info(\n-                        f'Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range}')\n+                        f'Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range} - Guideline: {record.guideline}')\n                 else:\n                     test_case.add_failure_info(\n                         f'Resource {record.resource} failed in check {check_name}')\n             if record.check_result['result'] == CheckResult.SKIPPED:\n                 test_case.add_skipped_info(\n-                    f'Resource {record.resource} skipped in check {check_name} \\n Suppress comment: {record.check_result[\"suppress_comment\"]}')\n+                    f'Resource {record.resource} skipped in check {check_name} \\n Suppress comment: {record.check_result[\"suppress_comment\"]} - Guideline: {record.guideline}')\n \n             test_cases[check_name].append(test_case)\n         for key in test_cases.keys():\n", "issue": "The junitxml output is missing the `guide` links\n**Describe the bug**\r\nThe junitxml output is missing the `guide` section. The output consists of, for example:\r\n\r\n```\r\n\t<testsuite disabled=\"0\" errors=\"0\" failures=\"0\" name=\"CKV_AZURE_14/Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service\" package=\"checkov.terraform.checks.resource.azure.AppServiceHTTPSOnly\" skipped=\"0\" tests=\"2\" time=\"0\">\r\n\t\t<testcase classname=\"checkov.terraform.checks.resource.azure.AppServiceHTTPSOnly\" file=\"plan.json\" name=\"None CKV_AZURE_14/Ensure web app redirects all HTTP traffic to HTTPS in Azure App Service azurerm_app_service.appservice\"/>\r\n\t</testsuite>\r\n```\r\nIt provides needed data, but it is missing a link to the guide. The issue was found when we tried to publish results in the Azure DevOps pipeline and saw there were no links to the solution.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. docker pull bridgecrew/checkov:2\r\n2. docker run --tty --volume dir:/dir bridgecrew/checkov -f /dir/plan.json --output junitxml > dir/report.xml\r\n\r\n**Expected behavior**\r\nLinks to the solution should be visible in Azure pipelines published results. \r\n\r\n**Desktop:**\r\n - OS: [ubuntu-20.04]\r\n - Checkov Version [2.0.240]\r\n\r\n\n", "before_files": [{"content": "import json\nfrom collections import defaultdict\nfrom typing import List, Dict, Union, Any, Optional\n\nfrom colorama import init\nfrom junit_xml import TestCase, TestSuite, to_xml_report_string\nfrom tabulate import tabulate\nfrom termcolor import colored\n\nfrom checkov.common.models.enums import CheckResult\nfrom checkov.common.output.record import Record\nfrom checkov.common.util.type_forcers import convert_csv_string_arg_to_list\nfrom checkov.version import version\n\ninit(autoreset=True)\n\n\nclass Report:\n    def __init__(self, check_type: str):\n        self.check_type: str = check_type\n        self.passed_checks: List[Record] = []\n        self.failed_checks: List[Record] = []\n        self.skipped_checks: List[Record] = []\n        self.parsing_errors: List[str] = []\n\n    def add_parsing_errors(self, errors: List[str]) -> None:\n        for file in errors:\n            self.add_parsing_error(file)\n\n    def add_parsing_error(self, file: str) -> None:\n        if file:\n            self.parsing_errors.append(file)\n\n    def add_record(self, record: Record) -> None:\n        if record.check_result['result'] == CheckResult.PASSED:\n            self.passed_checks.append(record)\n        if record.check_result['result'] == CheckResult.FAILED:\n            self.failed_checks.append(record)\n        if record.check_result['result'] == CheckResult.SKIPPED:\n            self.skipped_checks.append(record)\n\n    def get_summary(self) -> Dict[str, Union[int, str]]:\n        return {\n            \"passed\": len(self.passed_checks),\n            \"failed\": len(self.failed_checks),\n            \"skipped\": len(self.skipped_checks),\n            \"parsing_errors\": len(self.parsing_errors),\n            \"resource_count\": self._count_resources(),\n            \"checkov_version\": version\n        }\n\n    def get_json(self) -> str:\n        return json.dumps(self.get_dict(), indent=4)\n\n    def get_dict(self, is_quiet=False) -> dict:\n        if is_quiet:\n            return {\n                \"check_type\": self.check_type,\n                \"results\": {\n                    \"failed_checks\": [check.__dict__ for check in self.failed_checks]\n                },\n                \"summary\": self.get_summary()\n            }\n        else:\n            return {\n                \"check_type\": self.check_type,\n                \"results\": {\n                    \"passed_checks\": [check.__dict__ for check in self.passed_checks],\n                    \"failed_checks\": [check.__dict__ for check in self.failed_checks],\n                    \"skipped_checks\": [check.__dict__ for check in self.skipped_checks],\n                    \"parsing_errors\": list(self.parsing_errors)\n                },\n                \"summary\": self.get_summary()\n            }\n\n    def get_exit_code(self, soft_fail: bool, soft_fail_on: Optional[list] = None,\n                      hard_fail_on: Optional[list] = None) -> int:\n        \"\"\"\n        Returns the appropriate exit code depending on the flags that are passed in.\n\n        :param soft_fail: If true, exit code is always 0. (default is false)\n        :param soft_fail_on: A list of checks that will return exit code 0 if they fail. Other failing checks will\n        result exit code 1.\n        :param hard_fail_on: A list of checks that will return exit code 1 if they fail. Other failing checks will\n        result exit code 0.\n        :return: Exit code 0 or 1.\n        \"\"\"\n        if soft_fail_on:\n            soft_fail_on = convert_csv_string_arg_to_list(soft_fail_on)\n            if all(check_id in soft_fail_on for check_id in\n                   (failed_check.check_id for failed_check in self.failed_checks)):\n                # List of \"failed checks\" is a subset of the \"soft fail on\" list.\n                return 0\n            else:\n                return 1\n        if hard_fail_on:\n            hard_fail_on = convert_csv_string_arg_to_list(hard_fail_on)\n            if any(check_id in hard_fail_on for check_id in\n                   (failed_check.check_id for failed_check in self.failed_checks)):\n                # Any check from the list of \"failed checks\" is in the list of \"hard fail on checks\".\n                return 1\n            else:\n                return 0\n        if soft_fail:\n            return 0\n        elif len(self.failed_checks) > 0:\n            return 1\n        return 0\n\n    def is_empty(self) -> bool:\n        return len(self.passed_checks + self.failed_checks + self.skipped_checks) + len(self.parsing_errors) == 0\n\n    def print_console(self, is_quiet=False, is_compact=False, created_baseline_path=None, baseline=None) -> None:\n        summary = self.get_summary()\n        print(colored(f\"{self.check_type} scan results:\", \"blue\"))\n        if self.parsing_errors:\n            message = \"\\nPassed checks: {}, Failed checks: {}, Skipped checks: {}, Parsing errors: {}\\n\".format(\n                summary[\"passed\"], summary[\"failed\"], summary[\"skipped\"], summary[\"parsing_errors\"])\n        else:\n            message = \"\\nPassed checks: {}, Failed checks: {}, Skipped checks: {}\\n\".format(\n                summary[\"passed\"], summary[\"failed\"], summary[\"skipped\"])\n        print(colored(message, \"cyan\"))\n        if not is_quiet:\n            for record in self.passed_checks:\n                print(record.to_string(compact=is_compact))\n        for record in self.failed_checks:\n            print(record.to_string(compact=is_compact))\n        if not is_quiet:\n            for record in self.skipped_checks:\n                print(record.to_string(compact=is_compact))\n\n        if not is_quiet:\n            for file in self.parsing_errors:\n                Report._print_parsing_error_console(file)\n\n        if created_baseline_path:\n            print(colored(f\"Created a checkov baseline file at {created_baseline_path}\", \"blue\"))\n\n        if baseline:\n            print(colored(\n                f\"Baseline analysis report using {baseline.path} - only new failed checks with respect to the baseline are reported\",\n                \"blue\"))\n\n\n    @staticmethod\n    def _print_parsing_error_console(file: str) -> None:\n        print(colored(f'Error parsing file {file}', 'red'))\n\n    def print_junit_xml(self) -> None:\n        ts = self.get_test_suites()\n        xml_string = self.get_junit_xml_string(ts)\n        print(xml_string)\n\n    @staticmethod\n    def get_junit_xml_string(ts: List[TestSuite]) -> str:\n        return to_xml_report_string(ts)\n\n    def print_failed_github_md(self) -> None:\n        result = []\n        for record in self.failed_checks:\n            result.append([record.check_id, record.file_path, record.resource, record.check_name, record.guideline])\n        print(tabulate(result, headers=[\"check_id\", \"file\", \"resource\", \"check_name\", \"guideline\"], tablefmt=\"github\",\n                       showindex=True))\n        print(\"\\n\\n---\\n\\n\")\n\n    def get_test_suites(self) -> List[TestSuite]:\n        test_cases = defaultdict(list)\n        test_suites = []\n        records = self.passed_checks + self.failed_checks + self.skipped_checks\n        for record in records:\n            check_name = f'{record.check_id}/{record.check_name}'\n\n            test_name = f'{self.check_type} {check_name} {record.resource}'\n            test_case = TestCase(name=test_name, file=record.file_path, classname=record.check_class)\n            if record.check_result['result'] == CheckResult.FAILED:\n                if record.file_path and record.file_line_range:\n                    test_case.add_failure_info(\n                        f'Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range}')\n                else:\n                    test_case.add_failure_info(\n                        f'Resource {record.resource} failed in check {check_name}')\n            if record.check_result['result'] == CheckResult.SKIPPED:\n                test_case.add_skipped_info(\n                    f'Resource {record.resource} skipped in check {check_name} \\n Suppress comment: {record.check_result[\"suppress_comment\"]}')\n\n            test_cases[check_name].append(test_case)\n        for key in test_cases.keys():\n            test_suites.append(\n                TestSuite(name=key, test_cases=test_cases[key], package=test_cases[key][0].classname))\n        return test_suites\n\n    def print_json(self) -> None:\n        print(self.get_json())\n\n    def _count_resources(self) -> int:\n        unique_resources = set()\n        for record in self.passed_checks + self.failed_checks:\n            unique_resources.add(record.file_path + '.' + record.resource)\n        return len(unique_resources)\n\n    @staticmethod\n    def enrich_plan_report(report: \"Report\", enriched_resources: Dict[str, Dict[str, Any]]) -> \"Report\":\n        # This enriches reports with the appropriate filepath, line numbers, and codeblock\n        for record in report.failed_checks:\n            enriched_resource = enriched_resources.get(record.resource)\n            if enriched_resource:\n                record.file_path = enriched_resource[\"scanned_file\"]\n                record.file_line_range = enriched_resource[\"entity_lines_range\"]\n                record.code_block = enriched_resource[\"entity_code_lines\"]\n        return report\n\n    @staticmethod\n    def handle_skipped_checks(report: \"Report\", enriched_resources: Dict[str, Dict[str, Any]]) -> \"Report\":\n        for record in report.failed_checks:\n            resource_skips = enriched_resources.get(record.resource, {}).get(\"skipped_checks\", [])\n            for skip in resource_skips:\n                if record.check_id in skip[\"id\"]:\n                    # Remove and re-add the record to make Checkov mark it as skipped\n                    report.failed_checks.remove(record)\n                    record.check_result[\"result\"] = CheckResult.SKIPPED\n                    record.check_result[\"suppress_comment\"] = skip[\"suppress_comment\"]\n                    report.add_record(record)\n        return report\n", "path": "checkov/common/output/report.py"}], "after_files": [{"content": "import json\nfrom collections import defaultdict\nfrom typing import List, Dict, Union, Any, Optional\n\nfrom colorama import init\nfrom junit_xml import TestCase, TestSuite, to_xml_report_string\nfrom tabulate import tabulate\nfrom termcolor import colored\n\nfrom checkov.common.models.enums import CheckResult\nfrom checkov.common.output.record import Record\nfrom checkov.common.util.type_forcers import convert_csv_string_arg_to_list\nfrom checkov.version import version\n\ninit(autoreset=True)\n\n\nclass Report:\n    def __init__(self, check_type: str):\n        self.check_type: str = check_type\n        self.passed_checks: List[Record] = []\n        self.failed_checks: List[Record] = []\n        self.skipped_checks: List[Record] = []\n        self.parsing_errors: List[str] = []\n\n    def add_parsing_errors(self, errors: List[str]) -> None:\n        for file in errors:\n            self.add_parsing_error(file)\n\n    def add_parsing_error(self, file: str) -> None:\n        if file:\n            self.parsing_errors.append(file)\n\n    def add_record(self, record: Record) -> None:\n        if record.check_result['result'] == CheckResult.PASSED:\n            self.passed_checks.append(record)\n        if record.check_result['result'] == CheckResult.FAILED:\n            self.failed_checks.append(record)\n        if record.check_result['result'] == CheckResult.SKIPPED:\n            self.skipped_checks.append(record)\n\n    def get_summary(self) -> Dict[str, Union[int, str]]:\n        return {\n            \"passed\": len(self.passed_checks),\n            \"failed\": len(self.failed_checks),\n            \"skipped\": len(self.skipped_checks),\n            \"parsing_errors\": len(self.parsing_errors),\n            \"resource_count\": self._count_resources(),\n            \"checkov_version\": version\n        }\n\n    def get_json(self) -> str:\n        return json.dumps(self.get_dict(), indent=4)\n\n    def get_dict(self, is_quiet=False) -> dict:\n        if is_quiet:\n            return {\n                \"check_type\": self.check_type,\n                \"results\": {\n                    \"failed_checks\": [check.__dict__ for check in self.failed_checks]\n                },\n                \"summary\": self.get_summary()\n            }\n        else:\n            return {\n                \"check_type\": self.check_type,\n                \"results\": {\n                    \"passed_checks\": [check.__dict__ for check in self.passed_checks],\n                    \"failed_checks\": [check.__dict__ for check in self.failed_checks],\n                    \"skipped_checks\": [check.__dict__ for check in self.skipped_checks],\n                    \"parsing_errors\": list(self.parsing_errors)\n                },\n                \"summary\": self.get_summary()\n            }\n\n    def get_exit_code(self, soft_fail: bool, soft_fail_on: Optional[list] = None,\n                      hard_fail_on: Optional[list] = None) -> int:\n        \"\"\"\n        Returns the appropriate exit code depending on the flags that are passed in.\n\n        :param soft_fail: If true, exit code is always 0. (default is false)\n        :param soft_fail_on: A list of checks that will return exit code 0 if they fail. Other failing checks will\n        result exit code 1.\n        :param hard_fail_on: A list of checks that will return exit code 1 if they fail. Other failing checks will\n        result exit code 0.\n        :return: Exit code 0 or 1.\n        \"\"\"\n        if soft_fail_on:\n            soft_fail_on = convert_csv_string_arg_to_list(soft_fail_on)\n            if all(check_id in soft_fail_on for check_id in\n                   (failed_check.check_id for failed_check in self.failed_checks)):\n                # List of \"failed checks\" is a subset of the \"soft fail on\" list.\n                return 0\n            else:\n                return 1\n        if hard_fail_on:\n            hard_fail_on = convert_csv_string_arg_to_list(hard_fail_on)\n            if any(check_id in hard_fail_on for check_id in\n                   (failed_check.check_id for failed_check in self.failed_checks)):\n                # Any check from the list of \"failed checks\" is in the list of \"hard fail on checks\".\n                return 1\n            else:\n                return 0\n        if soft_fail:\n            return 0\n        elif len(self.failed_checks) > 0:\n            return 1\n        return 0\n\n    def is_empty(self) -> bool:\n        return len(self.passed_checks + self.failed_checks + self.skipped_checks) + len(self.parsing_errors) == 0\n\n    def print_console(self, is_quiet=False, is_compact=False, created_baseline_path=None, baseline=None) -> None:\n        summary = self.get_summary()\n        print(colored(f\"{self.check_type} scan results:\", \"blue\"))\n        if self.parsing_errors:\n            message = \"\\nPassed checks: {}, Failed checks: {}, Skipped checks: {}, Parsing errors: {}\\n\".format(\n                summary[\"passed\"], summary[\"failed\"], summary[\"skipped\"], summary[\"parsing_errors\"])\n        else:\n            message = \"\\nPassed checks: {}, Failed checks: {}, Skipped checks: {}\\n\".format(\n                summary[\"passed\"], summary[\"failed\"], summary[\"skipped\"])\n        print(colored(message, \"cyan\"))\n        if not is_quiet:\n            for record in self.passed_checks:\n                print(record.to_string(compact=is_compact))\n        for record in self.failed_checks:\n            print(record.to_string(compact=is_compact))\n        if not is_quiet:\n            for record in self.skipped_checks:\n                print(record.to_string(compact=is_compact))\n\n        if not is_quiet:\n            for file in self.parsing_errors:\n                Report._print_parsing_error_console(file)\n\n        if created_baseline_path:\n            print(colored(f\"Created a checkov baseline file at {created_baseline_path}\", \"blue\"))\n\n        if baseline:\n            print(colored(\n                f\"Baseline analysis report using {baseline.path} - only new failed checks with respect to the baseline are reported\",\n                \"blue\"))\n\n\n    @staticmethod\n    def _print_parsing_error_console(file: str) -> None:\n        print(colored(f'Error parsing file {file}', 'red'))\n\n    def print_junit_xml(self) -> None:\n        ts = self.get_test_suites()\n        xml_string = self.get_junit_xml_string(ts)\n        print(xml_string)\n\n    @staticmethod\n    def get_junit_xml_string(ts: List[TestSuite]) -> str:\n        return to_xml_report_string(ts)\n\n    def print_failed_github_md(self) -> None:\n        result = []\n        for record in self.failed_checks:\n            result.append([record.check_id, record.file_path, record.resource, record.check_name, record.guideline])\n        print(tabulate(result, headers=[\"check_id\", \"file\", \"resource\", \"check_name\", \"guideline\"], tablefmt=\"github\",\n                       showindex=True))\n        print(\"\\n\\n---\\n\\n\")\n\n    def get_test_suites(self) -> List[TestSuite]:\n        test_cases = defaultdict(list)\n        test_suites = []\n        records = self.passed_checks + self.failed_checks + self.skipped_checks\n        for record in records:\n            check_name = f'{record.check_id}/{record.check_name}'\n\n            test_name = f'{self.check_type} {check_name} {record.resource}'\n            test_case = TestCase(name=test_name, file=record.file_path, classname=record.check_class)\n            if record.check_result['result'] == CheckResult.FAILED:\n                if record.file_path and record.file_line_range:\n                    test_case.add_failure_info(\n                        f'Resource {record.resource} failed in check {check_name} - {record.file_path}:{record.file_line_range} - Guideline: {record.guideline}')\n                else:\n                    test_case.add_failure_info(\n                        f'Resource {record.resource} failed in check {check_name}')\n            if record.check_result['result'] == CheckResult.SKIPPED:\n                test_case.add_skipped_info(\n                    f'Resource {record.resource} skipped in check {check_name} \\n Suppress comment: {record.check_result[\"suppress_comment\"]} - Guideline: {record.guideline}')\n\n            test_cases[check_name].append(test_case)\n        for key in test_cases.keys():\n            test_suites.append(\n                TestSuite(name=key, test_cases=test_cases[key], package=test_cases[key][0].classname))\n        return test_suites\n\n    def print_json(self) -> None:\n        print(self.get_json())\n\n    def _count_resources(self) -> int:\n        unique_resources = set()\n        for record in self.passed_checks + self.failed_checks:\n            unique_resources.add(record.file_path + '.' + record.resource)\n        return len(unique_resources)\n\n    @staticmethod\n    def enrich_plan_report(report: \"Report\", enriched_resources: Dict[str, Dict[str, Any]]) -> \"Report\":\n        # This enriches reports with the appropriate filepath, line numbers, and codeblock\n        for record in report.failed_checks:\n            enriched_resource = enriched_resources.get(record.resource)\n            if enriched_resource:\n                record.file_path = enriched_resource[\"scanned_file\"]\n                record.file_line_range = enriched_resource[\"entity_lines_range\"]\n                record.code_block = enriched_resource[\"entity_code_lines\"]\n        return report\n\n    @staticmethod\n    def handle_skipped_checks(report: \"Report\", enriched_resources: Dict[str, Dict[str, Any]]) -> \"Report\":\n        for record in report.failed_checks:\n            resource_skips = enriched_resources.get(record.resource, {}).get(\"skipped_checks\", [])\n            for skip in resource_skips:\n                if record.check_id in skip[\"id\"]:\n                    # Remove and re-add the record to make Checkov mark it as skipped\n                    report.failed_checks.remove(record)\n                    record.check_result[\"result\"] = CheckResult.SKIPPED\n                    record.check_result[\"suppress_comment\"] = skip[\"suppress_comment\"]\n                    report.add_record(record)\n        return report\n", "path": "checkov/common/output/report.py"}]}