From 9d63816044388f5c2bd9ee7508962c94477b0fc7 Mon Sep 17 00:00:00 2001 From: Conner Crosby Date: Thu, 19 Sep 2024 09:10:35 -0400 Subject: [PATCH] Handle bare exception case from nested jinja2 vars (#4315) --- examples/playbooks/jinja-nested-vars.yml | 11 +++++++ src/ansiblelint/rules/jinja.py | 42 +++++++++++++++++++++--- tox.ini | 2 +- 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 examples/playbooks/jinja-nested-vars.yml diff --git a/examples/playbooks/jinja-nested-vars.yml b/examples/playbooks/jinja-nested-vars.yml new file mode 100644 index 0000000000..c6a0640cf6 --- /dev/null +++ b/examples/playbooks/jinja-nested-vars.yml @@ -0,0 +1,11 @@ +--- +- name: Test + gather_facts: false + hosts: + - localhost + tasks: + - name: Test + ansible.builtin.debug: + msg: "{{ cron_hour_raw }}" + vars: + cron_hour_raw: "{{ 12 | random(seed=inventory_hostname) }}" diff --git a/src/ansiblelint/rules/jinja.py b/src/ansiblelint/rules/jinja.py index ff124a8af6..b83cd89f06 100644 --- a/src/ansiblelint/rules/jinja.py +++ b/src/ansiblelint/rules/jinja.py @@ -23,7 +23,11 @@ from ansiblelint.runner import get_matches from ansiblelint.skip_utils import get_rule_skips_from_line from ansiblelint.text import has_jinja -from ansiblelint.utils import parse_yaml_from_file, template +from ansiblelint.utils import ( # type: ignore[attr-defined] + Templar, + parse_yaml_from_file, + template, +) from ansiblelint.yaml_utils import deannotate, nested_items_path if TYPE_CHECKING: @@ -95,7 +99,10 @@ class JinjaRule(AnsibleLintRule, TransformMixin): tags = ["formatting"] version_added = "v6.5.0" _ansible_error_re = re.compile( - r"^(?P.*): (?P.*)\. String: (?P.*)$", + ( + r"^(?P.*): (?P.*)\. String: (?P.*)$" + r"|An unhandled exception occurred while templating '.*'\. Error was a .*, original message: (?P.*)" + ), flags=re.MULTILINE, ) @@ -160,13 +167,20 @@ def matchtask( ): error = match.group("error") detail = match.group("detail") - if error.startswith( + nested_error = match.group("nested_error") + if error and error.startswith( "template error while templating string", ): bypass = False - elif detail.startswith("unable to locate collection"): + elif detail and detail.startswith( + "unable to locate collection", + ): _logger.debug("Ignored AnsibleError: %s", exc) bypass = True + elif nested_error and nested_error.startswith( + "Unexpected templating type error occurred on", + ): + bypass = True else: bypass = False elif re.match(r"^lookup plugin (.*) not found$", exc.message): @@ -863,6 +877,26 @@ def test_jinja_transform( assert expected_content == transformed_content playbook.with_suffix(f".tmp{playbook.suffix}").unlink() + def test_jinja_nested_var_errors() -> None: + """Tests our ability to handle nested var errors from jinja2 templates.""" + + def _do_template(*args, **kwargs): # type: ignore[no-untyped-def] # Templar.do_template has no type hint + data = args[1] + + if data != "{{ 12 | random(seed=inventory_hostname) }}": + return do_template(*args, **kwargs) + + msg = "Unexpected templating type error occurred on (foo): bar" + raise AnsibleError(msg) + + do_template = Templar.do_template + collection = RulesCollection() + collection.register(JinjaRule()) + lintable = Lintable("examples/playbooks/jinja-nested-vars.yml") + with mock.patch.object(Templar, "do_template", _do_template): + results = Runner(lintable, rules=collection).run() + assert len(results) == 0 + def _get_error_line(task: dict[str, Any], path: list[str | int]) -> int: """Return error line number.""" diff --git a/tox.ini b/tox.ini index e08b190013..fa2dc62718 100644 --- a/tox.ini +++ b/tox.ini @@ -74,7 +74,7 @@ setenv = PRE_COMMIT_COLOR = always # Number of expected test passes, safety measure for accidental skip of # tests. Update value if you add/remove tests. (tox-extra) - PYTEST_REQPASS = 892 + PYTEST_REQPASS = 893 FORCE_COLOR = 1 pre: PIP_PRE = 1 allowlist_externals =