Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion checkov/azure_pipelines/runner.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import os
import re
from typing import TYPE_CHECKING, Any, Optional

from checkov.azure_pipelines.checks.registry import registry
Expand All @@ -13,6 +15,24 @@
from collections.abc import Iterable


# File-name suffixes that the runner recognizes as Azure Pipelines configs out
# of the box. Supplemental suffixes can be supplied via the
# CHECKOV_AZURE_PIPELINES_FILE_NAMES environment variable as a comma- or
# whitespace-separated list (e.g. "ci.yml,pr-pipeline.yaml" or
# ".azuredevops/pr-pipeline.yaml").
DEFAULT_AZURE_PIPELINES_FILE_NAMES: tuple[str, ...] = (
'azure-pipelines.yml',
'azure-pipelines.yaml',
)


def _extra_pipelines_file_names() -> tuple[str, ...]:
raw = os.environ.get('CHECKOV_AZURE_PIPELINES_FILE_NAMES')
if not raw:
return ()
return tuple(part.strip() for part in re.split(r'[,\s]+', raw) if part.strip())


class Runner(YamlRunner):
check_type = CheckType.AZURE_PIPELINES # noqa: CCE003 # a static attribute

Expand All @@ -32,7 +52,8 @@ def _parse_file(

@staticmethod
def is_workflow_file(file_path: str) -> bool:
return file_path.endswith(('azure-pipelines.yml', 'azure-pipelines.yaml'))
suffixes = DEFAULT_AZURE_PIPELINES_FILE_NAMES + _extra_pipelines_file_names()
return file_path.endswith(suffixes)

def get_resource(self, file_path: str, key: str, supported_entities: Iterable[str],
start_line: int = -1, end_line: int = -1, graph_resource: bool = False) -> str:
Expand Down
65 changes: 65 additions & 0 deletions tests/azure_pipelines/test_is_workflow_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from __future__ import annotations

import os
import unittest
from unittest import mock

from checkov.azure_pipelines.runner import (
DEFAULT_AZURE_PIPELINES_FILE_NAMES,
Runner,
_extra_pipelines_file_names,
)


class TestIsWorkflowFile(unittest.TestCase):
"""Coverage for the file-name detection logic exercised by
``Runner.is_workflow_file`` after introducing
``CHECKOV_AZURE_PIPELINES_FILE_NAMES``."""

def test_default_file_names_are_recognized(self):
for name in DEFAULT_AZURE_PIPELINES_FILE_NAMES:
self.assertTrue(Runner.is_workflow_file(name))
self.assertTrue(Runner.is_workflow_file(f"path/to/{name}"))

def test_unrelated_yaml_is_not_recognized(self):
self.assertFalse(Runner.is_workflow_file("some-other-pipeline.yml"))
self.assertFalse(Runner.is_workflow_file(".github/workflows/ci.yml"))

def test_extra_file_names_via_env_var_comma(self):
env = {"CHECKOV_AZURE_PIPELINES_FILE_NAMES": "ci.yml,pr-pipeline.yaml"}
with mock.patch.dict(os.environ, env, clear=False):
self.assertTrue(Runner.is_workflow_file("ci.yml"))
self.assertTrue(Runner.is_workflow_file(".azuredevops/pr-pipeline.yaml"))
# Defaults still work
self.assertTrue(Runner.is_workflow_file("azure-pipelines.yml"))
# Unrelated names still rejected
self.assertFalse(Runner.is_workflow_file("foo.yml"))

def test_extra_file_names_via_env_var_whitespace(self):
env = {"CHECKOV_AZURE_PIPELINES_FILE_NAMES": "ci.yml pr-pipeline.yaml\tnightly.yml"}
with mock.patch.dict(os.environ, env, clear=False):
for name in ("ci.yml", "pr-pipeline.yaml", "nightly.yml"):
self.assertTrue(Runner.is_workflow_file(name), msg=name)

def test_empty_env_var_falls_back_to_defaults(self):
env = {"CHECKOV_AZURE_PIPELINES_FILE_NAMES": ""}
with mock.patch.dict(os.environ, env, clear=False):
self.assertEqual(_extra_pipelines_file_names(), ())
self.assertTrue(Runner.is_workflow_file("azure-pipelines.yml"))
self.assertFalse(Runner.is_workflow_file("ci.yml"))

def test_env_var_with_only_separators_yields_no_extras(self):
env = {"CHECKOV_AZURE_PIPELINES_FILE_NAMES": " , ,\t "}
with mock.patch.dict(os.environ, env, clear=False):
self.assertEqual(_extra_pipelines_file_names(), ())

def test_env_var_does_not_persist_across_unset(self):
env = {"CHECKOV_AZURE_PIPELINES_FILE_NAMES": "ci.yml"}
with mock.patch.dict(os.environ, env, clear=False):
self.assertTrue(Runner.is_workflow_file("ci.yml"))
# After the env var is gone, the extra suffix should no longer match.
self.assertFalse(Runner.is_workflow_file("ci.yml"))


if __name__ == "__main__":
unittest.main()
Loading