From 38a691305b3fb2fb4595abf7932d7297cf773399 Mon Sep 17 00:00:00 2001 From: Paul Hooijenga Date: Wed, 18 Jun 2025 17:56:07 +0200 Subject: [PATCH] fix(helm): support multiple resources in one template --- checkov/helm/runner.py | 45 ++++++++----------- .../helm/runner/resources/multiple/Chart.yaml | 4 ++ .../resources/multiple/templates/test.yaml | 12 +++++ .../runner/resources/multiple/values.yaml | 0 tests/helm/test_runner_multiple_resources.py | 29 ++++++++++++ 5 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 tests/helm/runner/resources/multiple/Chart.yaml create mode 100644 tests/helm/runner/resources/multiple/templates/test.yaml create mode 100644 tests/helm/runner/resources/multiple/values.yaml create mode 100644 tests/helm/test_runner_multiple_resources.py diff --git a/checkov/helm/runner.py b/checkov/helm/runner.py index c3ed662b7c..f2604631e7 100644 --- a/checkov/helm/runner.py +++ b/checkov/helm/runner.py @@ -171,48 +171,39 @@ def check_system_deps(self) -> str | None: @staticmethod def _parse_output(target_dir: str, output: bytes) -> None: + # Helm output is a series of YAML documents separated by '---' (document start marker). + # Each template starts with a comment line that indicates the source file, but a template + # may contain multiple documents (and can itself start with a marker). output_str = str(output, 'utf-8') reader = io.StringIO(output_str) cur_source_file = None cur_writer = None last_line_dashes = False - line_num = 1 for s in reader: s = s.rstrip() if s == '---': last_line_dashes = True - continue - - if last_line_dashes: - # The next line should contain a "Source" comment saying the name of the file it came from - # So we will close the old file, open a new file, and write the dashes from last iteration plus this line - - if not s.startswith('# Source: '): - raise Exception(f'Line {line_num}: Expected line to start with # Source: {s}') - source = s[10:] - if source != cur_source_file: - if cur_writer: - cur_writer.close() - file_path = os.path.join(target_dir, source) - parent = os.path.dirname(file_path) - os.makedirs(parent, exist_ok=True) - cur_source_file = source - cur_writer = open(os.path.join(target_dir, source), 'a') + + elif last_line_dashes: + if s.startswith('# Source: '): + source = s[10:] + if source != cur_source_file: + if cur_writer: + cur_writer.close() + file_path = os.path.join(target_dir, source) + parent = os.path.dirname(file_path) + os.makedirs(parent, exist_ok=True) + cur_source_file = source + cur_writer = open(os.path.join(target_dir, source), 'a') + if cur_writer: cur_writer.write('---' + os.linesep) cur_writer.write(s + os.linesep) last_line_dashes = False - else: - if s.startswith('# Source: '): - raise Exception(f'Line {line_num}: Unexpected line starting with # Source: {s}') - - if not cur_writer: - continue - else: - cur_writer.write(s + os.linesep) - line_num += 1 + elif cur_writer: + cur_writer.write(s + os.linesep) if cur_writer: cur_writer.close() diff --git a/tests/helm/runner/resources/multiple/Chart.yaml b/tests/helm/runner/resources/multiple/Chart.yaml new file mode 100644 index 0000000000..f9f88bd342 --- /dev/null +++ b/tests/helm/runner/resources/multiple/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: multiple-resources +description: A test Helm chart +version: 0.1.0 diff --git a/tests/helm/runner/resources/multiple/templates/test.yaml b/tests/helm/runner/resources/multiple/templates/test.yaml new file mode 100644 index 0000000000..55d0aeaa49 --- /dev/null +++ b/tests/helm/runner/resources/multiple/templates/test.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: one +data: + key: "value" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: two diff --git a/tests/helm/runner/resources/multiple/values.yaml b/tests/helm/runner/resources/multiple/values.yaml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/helm/test_runner_multiple_resources.py b/tests/helm/test_runner_multiple_resources.py new file mode 100644 index 0000000000..4a9620978b --- /dev/null +++ b/tests/helm/test_runner_multiple_resources.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +from checkov.common.output.report import CheckType +from checkov.helm.runner import Runner +from tests.helm.utils import helm_exists + +RESOURCES_PATH = Path(__file__).parent / "runner" / "resources" + + +@pytest.mark.skipif(not helm_exists(), reason="helm not installed") +def test_multiple_resources(): + runner = Runner() + report = runner.run( + root_folder=str(RESOURCES_PATH / "multiple"), + ) + + # The helm chart contains one template with two resources. + # Neither resource has a namespace. + + assert len(report.failed_checks) == 2 + assert report.check_type == CheckType.HELM + + assert len(report.resources) == 2 + for resource in report.resources: + assert "templates/test.yaml" in resource