Skip to content

Commit

Permalink
Improve jinja2 rule error handling (#3039)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssbarnea committed Feb 15, 2023
1 parent 2d06a15 commit cacaa9a
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 72 deletions.
155 changes: 85 additions & 70 deletions src/ansiblelint/rules/jinja.py
Expand Up @@ -9,7 +9,7 @@

import black
import jinja2
from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleParserError
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.parsing.yaml.objects import AnsibleUnicode
from jinja2.exceptions import TemplateSyntaxError

Expand All @@ -29,6 +29,19 @@

Token = namedtuple("Token", "lineno token_type value")

ignored_re = re.compile(
"|".join(
[
r"^Object of type method is not JSON serializable",
r"^Unexpected templating type error occurred on",
r"^obj must be a list of dicts or a nested dict$",
r"^the template file (.*) could not be found for the lookup$",
r"could not locate file in lookup",
r"unable to locate collection",
]
)
)


class JinjaRule(AnsibleLintRule):
"""Rule that looks inside jinja2 templates."""
Expand All @@ -55,80 +68,82 @@ def _msg(self, tag: str, value: str, reformatted: str) -> str:
def matchtask( # noqa: C901
self, task: dict[str, Any], file: Lintable | None = None
) -> bool | str | MatchError:
for key, v, _ in nested_items_path(task):
if isinstance(v, str):
try:
template(
basedir=file.dir if file else ".",
value=v,
variables=deannotate(task.get("vars", {})),
fail_on_error=True, # we later decide which ones to ignore or not
)
# ValueError RepresenterError
except AnsibleError as exc:
bypass = False
orig_exc = exc.orig_exc if getattr(exc, "orig_exc", None) else exc
match = self._ansible_error_re.match(
getattr(orig_exc, "message", str(orig_exc))
)
if (
isinstance(exc, AnsibleFilterError)
or "unable to locate collection" in orig_exc.message
):
bypass = True
elif re.match(
r"^the template file (.*) could not be found for the lookup$",
orig_exc.message,
) or re.match(r"could not locate file in lookup", orig_exc.message):
bypass = True
elif isinstance(orig_exc, AnsibleParserError):
# "An unhandled exception occurred while running the lookup plugin '...'. Error was a <class 'ansible.errors.AnsibleParserError'>, original message: Invalid filename: 'None'. Invalid filename: 'None'"

# An unhandled exception occurred while running the lookup plugin 'template'. Error was a <class 'ansible.errors.AnsibleError'>, original message: the template file ... could not be found for the lookup. the template file ... could not be found for the lookup

# ansible@devel (2.14) new behavior:
# AnsibleError(TemplateSyntaxError): template error while templating string: Could not load "ipwrap": 'Invalid plugin FQCN (ansible.netcommon.ipwrap): unable to locate collection ansible.netcommon'. String: Foo {{ buildset_registry.host | ipwrap }}. Could not load "ipwrap": 'Invalid plugin FQCN (ansible.netcommon.ipwrap): unable to locate collection ansible.netcommon'
bypass = True
elif (
isinstance(orig_exc, (AnsibleError, TemplateSyntaxError))
and match
):
error = match.group("error")
detail = match.group("detail")
# string = match.group("string")
if error.startswith("template error while templating string"):
bypass = False
elif detail.startswith("unable to locate collection"):
_logger.debug("Ignored AnsibleError: %s", exc)
try:
for key, v, _ in nested_items_path(task):
if isinstance(v, str):
try:
template(
basedir=file.dir if file else ".",
value=v,
variables=deannotate(task.get("vars", {})),
fail_on_error=True, # we later decide which ones to ignore or not
)
# ValueError RepresenterError
except AnsibleError as exc:
bypass = False
orig_exc = (
exc.orig_exc if getattr(exc, "orig_exc", None) else exc
)
orig_exc_message = getattr(orig_exc, "message", str(orig_exc))
match = self._ansible_error_re.match(
getattr(orig_exc, "message", str(orig_exc))
)
if ignored_re.match(orig_exc_message):
bypass = True
elif isinstance(orig_exc, AnsibleParserError):
# "An unhandled exception occurred while running the lookup plugin '...'. Error was a <class 'ansible.errors.AnsibleParserError'>, original message: Invalid filename: 'None'. Invalid filename: 'None'"

# An unhandled exception occurred while running the lookup plugin 'template'. Error was a <class 'ansible.errors.AnsibleError'>, original message: the template file ... could not be found for the lookup. the template file ... could not be found for the lookup

# ansible@devel (2.14) new behavior:
# AnsibleError(TemplateSyntaxError): template error while templating string: Could not load "ipwrap": 'Invalid plugin FQCN (ansible.netcommon.ipwrap): unable to locate collection ansible.netcommon'. String: Foo {{ buildset_registry.host | ipwrap }}. Could not load "ipwrap": 'Invalid plugin FQCN (ansible.netcommon.ipwrap): unable to locate collection ansible.netcommon'
bypass = True
elif (
isinstance(orig_exc, (AnsibleError, TemplateSyntaxError))
and match
):
error = match.group("error")
detail = match.group("detail")
# string = match.group("string")
if error.startswith(
"template error while templating string"
):
bypass = False
elif detail.startswith("unable to locate collection"):
_logger.debug("Ignored AnsibleError: %s", exc)
bypass = True
else:
bypass = False
elif re.match(r"^lookup plugin (.*) not found$", exc.message):
# lookup plugin 'template' not found
bypass = True
else:
bypass = False
elif re.match(r"^lookup plugin (.*) not found$", exc.message):
# lookup plugin 'template' not found
bypass = True

# AnsibleFilterError: 'obj must be a list of dicts or a nested dict'
# AnsibleError: template error while templating string: expected token ':', got '}'. String: {{ {{ '1' }} }}
# AnsibleError: template error while templating string: unable to locate collection ansible.netcommon. String: Foo {{ buildset_registry.host | ipwrap }}
if not bypass:

# AnsibleFilterError: 'obj must be a list of dicts or a nested dict'
# AnsibleError: template error while templating string: expected token ':', got '}'. String: {{ {{ '1' }} }}
# AnsibleError: template error while templating string: unable to locate collection ansible.netcommon. String: Foo {{ buildset_registry.host | ipwrap }}
if not bypass:
return self.create_matcherror(
message=str(exc),
linenumber=task[LINE_NUMBER_KEY],
filename=file,
tag=f"{self.id}[invalid]",
)
reformatted, details, tag = self.check_whitespace(
v, key=key, lintable=file
)
if reformatted != v:
return self.create_matcherror(
message=str(exc),
message=self._msg(
tag=tag, value=v, reformatted=reformatted
),
linenumber=task[LINE_NUMBER_KEY],
details=details,
filename=file,
tag=f"{self.id}[invalid]",
tag=f"{self.id}[{tag}]",
)

reformatted, details, tag = self.check_whitespace(
v, key=key, lintable=file
)
if reformatted != v:
return self.create_matcherror(
message=self._msg(tag=tag, value=v, reformatted=reformatted),
linenumber=task[LINE_NUMBER_KEY],
details=details,
filename=file,
tag=f"{self.id}[{tag}]",
)
except Exception as exc:
_logger.info("Exception in JinjaRule.matchtask: %s", exc)
raise
return False

def matchyaml(self, file: Lintable) -> list[MatchError]:
Expand Down
2 changes: 0 additions & 2 deletions src/ansiblelint/rules/role_name.py
Expand Up @@ -90,7 +90,6 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:

if file.kind == "playbook":
for play in file.data:
# breakpoint()
if "roles" in play:
line = play["__line__"]
for role in play["roles"]:
Expand All @@ -100,7 +99,6 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
elif isinstance(role, str):
role_name = role
if "/" in role_name:
# breakpoint()
result.append(
self.create_matcherror(
f"Avoid using paths when importing roles. ({role_name})",
Expand Down

0 comments on commit cacaa9a

Please sign in to comment.