""" Test to detect double Jinja2 templating issues in YAML files. This test prevents Ansible 12+ errors from embedded templates in Jinja2 expressions. The pattern `{{ lookup('file', '{{ var }}') }}` is invalid and must be `{{ lookup('file', var) }}` instead. Issue: https://github.com/trailofbits/algo/issues/14835 """ import re from pathlib import Path import pytest def find_yaml_files() -> list[Path]: """Find all YAML files in the repository.""" repo_root = Path(__file__).parent.parent.parent yaml_files = [] # Include all .yml and .yaml files for pattern in ["**/*.yml", "**/*.yaml"]: yaml_files.extend(repo_root.glob(pattern)) # Exclude test files and vendor directories excluded_dirs = {"venv", ".venv", "env", ".git", "__pycache__", ".pytest_cache"} yaml_files = [ f for f in yaml_files if not any(excluded in f.parts for excluded in excluded_dirs) ] return sorted(yaml_files) def detect_double_templating(content: str) -> list[tuple[int, str]]: """ Detect double templating patterns in file content. Returns list of (line_number, problematic_line) tuples. """ issues = [] # Pattern 1: lookup() with embedded {{ }} # Matches: lookup('file', '{{ var }}') or lookup("file", "{{ var }}") pattern1 = r"lookup\s*\([^)]*['\"]{{[^}]*}}['\"][^)]*\)" # Pattern 2: Direct nested {{ {{ }} }} pattern2 = r"{{\s*[^}]*{{\s*[^}]*}}" # Pattern 3: Embedded templates in quoted strings within Jinja2 # This catches cases like value: "{{ '{{ var }}' }}" pattern3 = r"{{\s*['\"][^'\"]*{{[^}]*}}[^'\"]*['\"]" lines = content.split('\n') for i, line in enumerate(lines, 1): # Skip comments stripped = line.split('#')[0] if not stripped.strip(): continue if (re.search(pattern1, stripped) or re.search(pattern2, stripped) or re.search(pattern3, stripped)): issues.append((i, line)) return issues def test_no_double_templating(): """Test that no YAML files contain double templating patterns.""" yaml_files = find_yaml_files() all_issues = {} for yaml_file in yaml_files: try: content = yaml_file.read_text() issues = detect_double_templating(content) if issues: # Store relative path for cleaner output rel_path = yaml_file.relative_to(Path(__file__).parent.parent.parent) all_issues[str(rel_path)] = issues except Exception: # Skip binary files or files we can't read continue if all_issues: # Format error message for clarity error_msg = "\n\nDouble templating issues found:\n" error_msg += "=" * 60 + "\n" for file_path, issues in all_issues.items(): error_msg += f"\n{file_path}:\n" for line_num, line in issues: error_msg += f" Line {line_num}: {line.strip()}\n" error_msg += "\n" + "=" * 60 + "\n" error_msg += "Fix: Replace '{{ var }}' with var inside lookup() calls\n" error_msg += "Example: lookup('file', '{{ SSH_keys.public }}') → lookup('file', SSH_keys.public)\n" pytest.fail(error_msg) def test_specific_known_issues(): """ Test for specific known double-templating issues. This ensures our detection catches the actual bugs from issue #14835. """ # These are the actual problematic patterns from the codebase known_bad_patterns = [ "{{ lookup('file', '{{ SSH_keys.public }}') }}", '{{ lookup("file", "{{ credentials_file_path }}") }}', "value: \"{{ lookup('file', '{{ SSH_keys.public }}') }}\"", "PayloadContentCA: \"{{ lookup('file' , '{{ ipsec_pki_path }}/cacert.pem')|b64encode }}\"", ] for pattern in known_bad_patterns: issues = detect_double_templating(pattern) assert issues, f"Failed to detect known bad pattern: {pattern}" def test_valid_patterns_not_flagged(): """ Test that valid templating patterns are not flagged as errors. """ valid_patterns = [ "{{ lookup('file', SSH_keys.public) }}", "{{ lookup('file', credentials_file_path) }}", "value: \"{{ lookup('file', SSH_keys.public) }}\"", "{{ item.1 }}.mobileconfig", "{{ loop.index }}. {{ r.server }} ({{ r.IP_subject_alt_name }})", "PayloadContentCA: \"{{ lookup('file', ipsec_pki_path + '/cacert.pem')|b64encode }}\"", "ssh_pub_key: \"{{ lookup('file', SSH_keys.public) }}\"", ] for pattern in valid_patterns: issues = detect_double_templating(pattern) assert not issues, f"Valid pattern incorrectly flagged: {pattern}" if __name__ == "__main__": # Run the test directly for debugging test_specific_known_issues() test_valid_patterns_not_flagged() test_no_double_templating() print("All tests passed!")