12
12
import warnings
13
13
from dataclasses import dataclass
14
14
from fnmatch import fnmatch
15
+ from pathlib import Path
15
16
from typing import TYPE_CHECKING , Any
16
17
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
17
22
from ansible_compat .runtime import AnsibleWarning
18
23
19
24
import ansiblelint .skip_utils
31
36
from ansiblelint .logger import timed_info
32
37
from ansiblelint .rules .syntax_check import OUTPUT_PATTERNS , AnsibleSyntaxCheckRule
33
38
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
+ )
34
46
35
47
if TYPE_CHECKING :
36
48
from collections .abc import Generator
37
- from pathlib import Path
49
+ from typing import Callable
38
50
39
51
from ansiblelint .config import Options
52
+ from ansiblelint .constants import FileType
40
53
from ansiblelint .rules import RulesCollection
41
54
42
55
_logger = logging .getLogger (__name__ )
@@ -415,7 +428,7 @@ def _emit_matches(self, files: list[Lintable]) -> Generator[MatchError, None, No
415
428
while visited != self .lintables :
416
429
for lintable in self .lintables - visited :
417
430
try :
418
- children = ansiblelint . utils .find_children (lintable )
431
+ children = self .find_children (lintable )
419
432
for child in children :
420
433
if self .is_excluded (child ):
421
434
continue
@@ -430,6 +443,93 @@ def _emit_matches(self, files: list[Lintable]) -> Generator[MatchError, None, No
430
443
yield MatchError (lintable = lintable , rule = LoadingFailureRule ())
431
444
visited .add (lintable )
432
445
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
+
433
533
434
534
def _get_matches (rules : RulesCollection , options : Options ) -> LintResult :
435
535
lintables = ansiblelint .utils .get_lintables (opts = options , args = options .lintables )
0 commit comments