Skip to content

Commit

Permalink
Improve logic of find_children
Browse files Browse the repository at this point in the history
Address few bugs in our implementation of find_children, making it
avoid errors when encountering garbage data.
  • Loading branch information
ssbarnea committed Jun 4, 2024
1 parent b5d027c commit 5e3e16e
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
env:
# Number of expected test passes, safety measure for accidental skip of
# tests. Update value if you add/remove tests.
PYTEST_REQPASS: 877
PYTEST_REQPASS: 878
steps:
- uses: actions/checkout@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions examples/playbooks/common-include-1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
- name: Some include_tasks with file and jinja2
ansible.builtin.include_tasks:
file: "{{ 'tasks/included-with-lint.yml' }}"
- name: Some include 3
ansible.builtin.include_tasks: file=tasks/included-with-lint.yml
9 changes: 9 additions & 0 deletions examples/playbooks/common-include-wrong-syntax.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
- name: Fixture for test coverage
hosts: localhost
gather_facts: false
tasks:
- name: Some include with invalid syntax
ansible.builtin.include_tasks: "file="
- name: Some include with invalid syntax
ansible.builtin.include_tasks: other=tasks/included-with-lint.yml
1 change: 1 addition & 0 deletions examples/playbooks/include.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
tasks:
- ansible.builtin.include_tasks: tasks/x.yml
- ansible.builtin.include_tasks: tasks/x.yml y=z
- ansible.builtin.include_tasks: file=tasks/x.yml

handlers:
- ansible.builtin.include_tasks: handlers/y.yml
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
---
- name: include_task_with_vars | Foo
- name: include_task_with_vars | Var1
ansible.builtin.include_tasks: file=../tasks/included-task-with-vars.yml

- name: include_task_with_vars | Var2
ansible.builtin.include_tasks: ../tasks/included-task-with-vars.yml
vars:
var_naming_pattern_1: bar
_var_naming_pattern_2: ... # we allow _ before the prefix
__var_naming_pattern_3: ... # we allow __ before the prefix

- name: include_task_with_vars | Foo
- name: include_task_with_vars | Var3
ansible.builtin.include_role:
name: bobbins
vars:
Expand Down
1 change: 1 addition & 0 deletions playbook.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
- name: Example
hosts: localhost
gather_facts: false
tasks:
- name: include extra tasks
ansible.builtin.include_tasks:
Expand Down
2 changes: 1 addition & 1 deletion src/ansiblelint/rules/role_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def _infer_role_name(meta: Path, default: str) -> str:
if meta_data:
try:
return str(meta_data["galaxy_info"]["role_name"])
except KeyError:
except (KeyError, TypeError):
pass
return default

Expand Down
10 changes: 7 additions & 3 deletions src/ansiblelint/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ def _emit_matches(self, files: list[Lintable]) -> Generator[MatchError, None, No
visited: set[Lintable] = set()
while visited != self.lintables:
for lintable in self.lintables - visited:
visited.add(lintable)
if not lintable.path.exists():
continue
try:
children = self.find_children(lintable)
for child in children:
Expand All @@ -468,8 +471,10 @@ def _emit_matches(self, files: list[Lintable]) -> Generator[MatchError, None, No
exc.rule = self.rules["load-failure"]
yield exc
except AttributeError:
yield MatchError(lintable=lintable, rule=self.rules["load-failure"])
visited.add(lintable)
yield MatchError(
lintable=lintable,
rule=self.rules["load-failure"],
)

def find_children(self, lintable: Lintable) -> list[Lintable]:
"""Traverse children of a single file or folder."""
Expand All @@ -490,7 +495,6 @@ def find_children(self, lintable: Lintable) -> list[Lintable]:
except AnsibleError as exc:
msg = f"Loading {lintable.filename} caused an {type(exc).__name__} exception: {exc}, file was ignored."
logging.exception(msg)
# raise SystemExit(exc) from exc
return []
results = []
# playbook_ds can be an AnsibleUnicode string, which we consider invalid
Expand Down
39 changes: 30 additions & 9 deletions src/ansiblelint/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import logging
import os
import re
from collections.abc import ItemsView, Iterator, Mapping, Sequence
from collections.abc import ItemsView, Iterable, Iterator, Mapping, Sequence
from dataclasses import _MISSING_TYPE, dataclass, field
from functools import cache, lru_cache
from pathlib import Path
Expand Down Expand Up @@ -290,7 +290,7 @@ class HandleChildren:
rules: RulesCollection = field(init=True, repr=False)
app: App

def include_children(
def include_children( # pylint: disable=too-many-return-statements
self,
lintable: Lintable,
k: str,
Expand Down Expand Up @@ -325,14 +325,21 @@ def include_children(
return []

# handle include: filename.yml tags=blah
(args, _) = tokenize(v)
(args, kwargs) = tokenize(v)

result = path_dwim(basedir, args[0])
if args:
file = args[0]
elif "file" in kwargs:
file = kwargs["file"]
else:
return []

result = path_dwim(basedir, file)
while basedir not in ["", "/"]:
if os.path.exists(result):
break
basedir = os.path.dirname(basedir)
result = path_dwim(basedir, args[0])
result = path_dwim(basedir, file)

return [Lintable(result, kind=parent_type)]

Expand Down Expand Up @@ -430,7 +437,7 @@ def roles_children(
# pylint: disable=unused-argument # parent_type)
basedir = str(lintable.path.parent)
results: list[Lintable] = []
if not v:
if not v or not isinstance(v, Iterable):
# typing does not prevent junk from being passed in
return results
for role in v:
Expand Down Expand Up @@ -467,10 +474,24 @@ def _get_task_handler_children_for_tasks_or_playbooks(
if not task_handler or isinstance(task_handler, str): # pragma: no branch
continue

file_name = task_handler[task_handler_key]
if isinstance(file_name, Mapping) and file_name.get("file", None):
file_name = file_name["file"]
file_name = ""
action_args = task_handler[task_handler_key]
if isinstance(action_args, str):
(args, kwargs) = tokenize(action_args)
if len(args) == 1:
file_name = args[0]
elif kwargs.get("file", None):
file_name = kwargs["file"]
else:
# ignore invalid data
continue

if isinstance(action_args, Mapping) and action_args.get("file", None):
file_name = action_args["file"]

if not file_name:
breakpoint()
continue
f = path_dwim(basedir, file_name)
while basedir not in ["", "/"]:
if os.path.exists(f):
Expand Down
17 changes: 17 additions & 0 deletions test/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,23 @@ def test_files_not_scanned_twice(default_rules_collection: RulesCollection) -> N
assert len(run2) == 0


def test_include_wrong_syntax(default_rules_collection: RulesCollection) -> None:
"""Ensure that lintables aren't double-checked."""
checked_files: set[Lintable] = set()

filename = Path("examples/playbooks/common-include-wrong-syntax.yml").resolve()
runner = Runner(
filename,
rules=default_rules_collection,
verbosity=0,
checked_files=checked_files,
)
result = runner.run()
assert len(runner.checked_files) == 1
assert len(result) == 1, result
assert result[0].tag == "syntax-check[specific]"


def test_runner_not_found(default_rules_collection: RulesCollection) -> None:
"""Ensure that lintables aren't double-checked."""
checked_files: set[Lintable] = set()
Expand Down
4 changes: 2 additions & 2 deletions test/test_yaml_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,12 +768,12 @@ def test_get_path_to_play(
pytest.param(
"examples/playbooks/include.yml",
14,
[0, "tasks", 1],
[0, "tasks", 2],
id="playbook-multi_tasks_blocks-tasks_last_task_before_handlers",
),
pytest.param(
"examples/playbooks/include.yml",
16,
17,
[0, "handlers", 0],
id="playbook-multi_tasks_blocks-handlers_task",
),
Expand Down

0 comments on commit 5e3e16e

Please sign in to comment.