Skip to content

Commit e2c1247

Browse files
authored
Adopt standalone utils functions into Runner (#3572)
1 parent f43b629 commit e2c1247

File tree

4 files changed

+113
-100
lines changed

4 files changed

+113
-100
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ repos:
156156
- rich>=13.2.0
157157
- ruamel-yaml>=0.17.31
158158
- ruamel-yaml-clib>=0.2.7
159-
- spdx-tools
159+
- spdx-tools>=0.7.1
160160
- subprocess-tee
161161
- types-PyYAML
162162
- types-jsonschema>=4.4.2
@@ -187,7 +187,7 @@ repos:
187187
- rich>=13.2.0
188188
- ruamel-yaml>=0.17.31
189189
- ruamel-yaml-clib>=0.2.7
190-
- spdx-tools
190+
- spdx-tools>=0.7.1
191191
- typing_extensions
192192
- wcmatch
193193
- yamllint

src/ansiblelint/runner.py

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@
1212
import warnings
1313
from dataclasses import dataclass
1414
from fnmatch import fnmatch
15+
from pathlib import Path
1516
from typing import TYPE_CHECKING, Any
1617

18+
from ansible.errors import AnsibleError
19+
from ansible.parsing.splitter import split_args
20+
from ansible.parsing.yaml.constructor import AnsibleMapping
21+
from ansible.plugins.loader import add_all_plugin_dirs
1722
from ansible_compat.runtime import AnsibleWarning
1823

1924
import ansiblelint.skip_utils
@@ -31,12 +36,20 @@
3136
from ansiblelint.logger import timed_info
3237
from ansiblelint.rules.syntax_check import OUTPUT_PATTERNS, AnsibleSyntaxCheckRule
3338
from ansiblelint.text import strip_ansi_escape
39+
from ansiblelint.utils import (
40+
PLAYBOOK_DIR,
41+
_include_children,
42+
_roles_children,
43+
_taskshandlers_children,
44+
template,
45+
)
3446

3547
if TYPE_CHECKING:
3648
from collections.abc import Generator
37-
from pathlib import Path
49+
from typing import Callable
3850

3951
from ansiblelint.config import Options
52+
from ansiblelint.constants import FileType
4053
from ansiblelint.rules import RulesCollection
4154

4255
_logger = logging.getLogger(__name__)
@@ -415,7 +428,7 @@ def _emit_matches(self, files: list[Lintable]) -> Generator[MatchError, None, No
415428
while visited != self.lintables:
416429
for lintable in self.lintables - visited:
417430
try:
418-
children = ansiblelint.utils.find_children(lintable)
431+
children = self.find_children(lintable)
419432
for child in children:
420433
if self.is_excluded(child):
421434
continue
@@ -430,6 +443,93 @@ def _emit_matches(self, files: list[Lintable]) -> Generator[MatchError, None, No
430443
yield MatchError(lintable=lintable, rule=LoadingFailureRule())
431444
visited.add(lintable)
432445

446+
def find_children(self, lintable: Lintable) -> list[Lintable]:
447+
"""Traverse children of a single file or folder."""
448+
if not lintable.path.exists():
449+
return []
450+
playbook_dir = str(lintable.path.parent)
451+
ansiblelint.utils.set_collections_basedir(lintable.path.parent)
452+
add_all_plugin_dirs(playbook_dir or ".")
453+
if lintable.kind == "role":
454+
playbook_ds = AnsibleMapping({"roles": [{"role": str(lintable.path)}]})
455+
elif lintable.kind not in ("playbook", "tasks"):
456+
return []
457+
else:
458+
try:
459+
playbook_ds = ansiblelint.utils.parse_yaml_from_file(str(lintable.path))
460+
except AnsibleError as exc:
461+
raise SystemExit(exc) from exc
462+
results = []
463+
# playbook_ds can be an AnsibleUnicode string, which we consider invalid
464+
if isinstance(playbook_ds, str):
465+
raise MatchError(lintable=lintable, rule=LoadingFailureRule())
466+
for item in ansiblelint.utils.playbook_items(playbook_ds):
467+
# if lintable.kind not in ["playbook"]:
468+
for child in self.play_children(
469+
lintable.path.parent,
470+
item,
471+
lintable.kind,
472+
playbook_dir,
473+
):
474+
# We avoid processing parametrized children
475+
path_str = str(child.path)
476+
if "$" in path_str or "{{" in path_str:
477+
continue
478+
479+
# Repair incorrect paths obtained when old syntax was used, like:
480+
# - include: simpletask.yml tags=nginx
481+
valid_tokens = []
482+
for token in split_args(path_str):
483+
if "=" in token:
484+
break
485+
valid_tokens.append(token)
486+
path = " ".join(valid_tokens)
487+
if path != path_str:
488+
child.path = Path(path)
489+
child.name = child.path.name
490+
491+
results.append(child)
492+
return results
493+
494+
def play_children(
495+
self,
496+
basedir: Path,
497+
item: tuple[str, Any],
498+
parent_type: FileType,
499+
playbook_dir: str,
500+
) -> list[Lintable]:
501+
"""Flatten the traversed play tasks."""
502+
# pylint: disable=unused-argument
503+
delegate_map: dict[str, Callable[[str, Any, Any, FileType], list[Lintable]]] = {
504+
"tasks": _taskshandlers_children,
505+
"pre_tasks": _taskshandlers_children,
506+
"post_tasks": _taskshandlers_children,
507+
"block": _taskshandlers_children,
508+
"include": _include_children,
509+
"ansible.builtin.include": _include_children,
510+
"import_playbook": _include_children,
511+
"ansible.builtin.import_playbook": _include_children,
512+
"roles": _roles_children,
513+
"dependencies": _roles_children,
514+
"handlers": _taskshandlers_children,
515+
"include_tasks": _include_children,
516+
"ansible.builtin.include_tasks": _include_children,
517+
"import_tasks": _include_children,
518+
"ansible.builtin.import_tasks": _include_children,
519+
}
520+
(k, v) = item
521+
add_all_plugin_dirs(str(basedir.resolve()))
522+
523+
if k in delegate_map and v:
524+
v = template(
525+
basedir,
526+
v,
527+
{"playbook_dir": PLAYBOOK_DIR or str(basedir.resolve())},
528+
fail_on_undefined=False,
529+
)
530+
return delegate_map[k](str(basedir), k, v, parent_type)
531+
return []
532+
433533

434534
def _get_matches(rules: RulesCollection, options: Options) -> LintResult:
435535
lintables = ansiblelint.utils.get_lintables(opts=options, args=options.lintables)

src/ansiblelint/utils.py

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,13 @@
3131
from dataclasses import _MISSING_TYPE, dataclass, field
3232
from functools import cache
3333
from pathlib import Path
34-
from typing import Any, Callable
34+
from typing import Any
3535

3636
import yaml
3737
from ansible.errors import AnsibleError, AnsibleParserError
3838
from ansible.module_utils.parsing.convert_bool import boolean
3939
from ansible.parsing.dataloader import DataLoader
4040
from ansible.parsing.mod_args import ModuleArgsParser
41-
from ansible.parsing.splitter import split_args
4241
from ansible.parsing.yaml.constructor import AnsibleConstructor, AnsibleMapping
4342
from ansible.parsing.yaml.loader import AnsibleLoader
4443
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleSequence
@@ -50,7 +49,6 @@
5049

5150
from ansiblelint._internal.rules import (
5251
AnsibleParserErrorRule,
53-
LoadingFailureRule,
5452
RuntimeErrorRule,
5553
)
5654
from ansiblelint.app import get_app
@@ -234,7 +232,8 @@ def tokenize(line: str) -> tuple[str, list[str], dict[str, str]]:
234232
return (command, args, kwargs)
235233

236234

237-
def _playbook_items(pb_data: AnsibleBaseYAMLObject) -> ItemsView: # type: ignore[type-arg]
235+
def playbook_items(pb_data: AnsibleBaseYAMLObject) -> ItemsView: # type: ignore[type-arg]
236+
"""Return a list of items from within the playbook."""
238237
if isinstance(pb_data, dict):
239238
return pb_data.items()
240239
if not pb_data:
@@ -246,62 +245,13 @@ def _playbook_items(pb_data: AnsibleBaseYAMLObject) -> ItemsView: # type: ignor
246245
return [item for play in pb_data if play for item in play.items()] # type: ignore[return-value]
247246

248247

249-
def _set_collections_basedir(basedir: Path) -> None:
250-
# Sets the playbook directory as playbook_paths for the collection loader
248+
def set_collections_basedir(basedir: Path) -> None:
249+
"""Set the playbook directory as playbook_paths for the collection loader."""
251250
# Ansible expects only absolute paths inside `playbook_paths` and will
252251
# produce weird errors if we use a relative one.
253252
AnsibleCollectionConfig.playbook_paths = str(basedir.resolve())
254253

255254

256-
def find_children(lintable: Lintable) -> list[Lintable]:
257-
"""Traverse children of a single file or folder."""
258-
if not lintable.path.exists():
259-
return []
260-
playbook_dir = str(lintable.path.parent)
261-
_set_collections_basedir(lintable.path.parent)
262-
add_all_plugin_dirs(playbook_dir or ".")
263-
if lintable.kind == "role":
264-
playbook_ds = AnsibleMapping({"roles": [{"role": str(lintable.path)}]})
265-
elif lintable.kind not in ("playbook", "tasks"):
266-
return []
267-
else:
268-
try:
269-
playbook_ds = parse_yaml_from_file(str(lintable.path))
270-
except AnsibleError as exc:
271-
raise SystemExit(exc) from exc
272-
results = []
273-
# playbook_ds can be an AnsibleUnicode string, which we consider invalid
274-
if isinstance(playbook_ds, str):
275-
raise MatchError(lintable=lintable, rule=LoadingFailureRule())
276-
for item in _playbook_items(playbook_ds):
277-
# if lintable.kind not in ["playbook"]:
278-
for child in play_children(
279-
lintable.path.parent,
280-
item,
281-
lintable.kind,
282-
playbook_dir,
283-
):
284-
# We avoid processing parametrized children
285-
path_str = str(child.path)
286-
if "$" in path_str or "{{" in path_str:
287-
continue
288-
289-
# Repair incorrect paths obtained when old syntax was used, like:
290-
# - include: simpletask.yml tags=nginx
291-
valid_tokens = []
292-
for token in split_args(path_str):
293-
if "=" in token:
294-
break
295-
valid_tokens.append(token)
296-
path = " ".join(valid_tokens)
297-
if path != path_str:
298-
child.path = Path(path)
299-
child.name = child.path.name
300-
301-
results.append(child)
302-
return results
303-
304-
305255
def template(
306256
basedir: Path,
307257
value: Any,
@@ -328,45 +278,6 @@ def template(
328278
return value
329279

330280

331-
def play_children(
332-
basedir: Path,
333-
item: tuple[str, Any],
334-
parent_type: FileType,
335-
playbook_dir: str, # noqa: ARG001
336-
) -> list[Lintable]:
337-
"""Flatten the traversed play tasks."""
338-
# pylint: disable=unused-argument
339-
delegate_map: dict[str, Callable[[str, Any, Any, FileType], list[Lintable]]] = {
340-
"tasks": _taskshandlers_children,
341-
"pre_tasks": _taskshandlers_children,
342-
"post_tasks": _taskshandlers_children,
343-
"block": _taskshandlers_children,
344-
"include": _include_children,
345-
"ansible.builtin.include": _include_children,
346-
"import_playbook": _include_children,
347-
"ansible.builtin.import_playbook": _include_children,
348-
"roles": _roles_children,
349-
"dependencies": _roles_children,
350-
"handlers": _taskshandlers_children,
351-
"include_tasks": _include_children,
352-
"ansible.builtin.include_tasks": _include_children,
353-
"import_tasks": _include_children,
354-
"ansible.builtin.import_tasks": _include_children,
355-
}
356-
(k, v) = item
357-
add_all_plugin_dirs(str(basedir.resolve()))
358-
359-
if k in delegate_map and v:
360-
v = template(
361-
basedir,
362-
v,
363-
{"playbook_dir": PLAYBOOK_DIR or str(basedir.resolve())},
364-
fail_on_undefined=False,
365-
)
366-
return delegate_map[k](str(basedir), k, v, parent_type)
367-
return []
368-
369-
370281
def _include_children(
371282
basedir: str,
372283
k: str,

test/test_utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,9 +402,11 @@ def test_get_rules_dirs_with_custom_rules(
402402
assert get_rules_dirs(user_ruledirs, use_default=use_default) == expected
403403

404404

405-
def test_find_children() -> None:
405+
def test_find_children(default_rules_collection: RulesCollection) -> None:
406406
"""Verify correct function of find_children()."""
407-
utils.find_children(Lintable("examples/playbooks/find_children.yml"))
407+
Runner(
408+
rules=default_rules_collection,
409+
).find_children(Lintable("examples/playbooks/find_children.yml"))
408410

409411

410412
def test_find_children_in_task(default_rules_collection: RulesCollection) -> None:

0 commit comments

Comments
 (0)