3
3
# Copyright (c) 2018, Ansible Project
4
4
from __future__ import annotations
5
5
6
+ import copy
7
+ import logging
6
8
import sys
7
9
from typing import TYPE_CHECKING
8
10
9
11
from ansiblelint .rules import AnsibleLintRule , TransformMixin
12
+ from ansiblelint .runner import _get_matches
13
+ from ansiblelint .transformer import Transformer
10
14
11
15
if TYPE_CHECKING :
16
+ from pathlib import Path
17
+
12
18
from ruamel .yaml .comments import CommentedMap , CommentedSeq
13
19
20
+ from ansiblelint .config import Options
14
21
from ansiblelint .errors import MatchError
15
22
from ansiblelint .file_utils import Lintable
16
23
from ansiblelint .utils import Task
17
24
18
25
26
+ _logger = logging .getLogger (__name__ )
27
+
28
+
19
29
class TaskNoLocalAction (AnsibleLintRule , TransformMixin ):
20
30
"""Do not use 'local_action', use 'delegate_to: localhost'."""
21
31
@@ -45,16 +55,31 @@ def transform(
45
55
data : CommentedMap | CommentedSeq | str ,
46
56
) -> None :
47
57
if match .tag == self .id :
48
- target_task = self .seek (match .yaml_path , data )
58
+ # we do not want perform a partial modification accidentally
59
+ original_target_task = self .seek (match .yaml_path , data )
60
+ target_task = copy .deepcopy (original_target_task )
49
61
for _ in range (len (target_task )):
50
62
k , v = target_task .popitem (False )
51
63
if k == "local_action" :
52
- module_name = v ["module" ]
53
- target_task [module_name ] = None
54
- target_task ["delegate_to" ] = "localhost"
64
+ if isinstance (v , dict ):
65
+ module_name = v ["module" ]
66
+ target_task [module_name ] = None
67
+ target_task ["delegate_to" ] = "localhost"
68
+ elif isinstance (v , str ):
69
+ module_name , module_value = v .split (" " , 1 )
70
+ target_task [module_name ] = module_value
71
+ target_task ["delegate_to" ] = "localhost"
72
+ else :
73
+ _logger .debug (
74
+ "Ignored unexpected data inside %s transform." ,
75
+ self .id ,
76
+ )
77
+ return
55
78
else :
56
79
target_task [k ] = v
57
80
match .fixed = True
81
+ original_target_task .clear ()
82
+ original_target_task .update (target_task )
58
83
59
84
60
85
# testing code to be loaded only with pytest or when executed the rule file
@@ -71,3 +96,35 @@ def test_local_action(default_rules_collection: RulesCollection) -> None:
71
96
72
97
assert len (results ) == 1
73
98
assert results [0 ].tag == "deprecated-local-action"
99
+
100
+ def test_local_action_transform (
101
+ config_options : Options ,
102
+ copy_examples_dir : tuple [Path , Path ],
103
+ default_rules_collection : RulesCollection ,
104
+ ) -> None :
105
+ """Test transform functionality for no-log-password rule."""
106
+ playbook : str = "examples/playbooks/tasks/local_action.yml"
107
+ config_options .write_list = ["all" ]
108
+
109
+ config_options .lintables = [playbook ]
110
+ runner_result = _get_matches (
111
+ rules = default_rules_collection ,
112
+ options = config_options ,
113
+ )
114
+ transformer = Transformer (result = runner_result , options = config_options )
115
+ transformer .run ()
116
+
117
+ matches = runner_result .matches
118
+ assert len (matches ) == 3
119
+
120
+ orig_dir , tmp_dir = copy_examples_dir
121
+ orig_playbook = orig_dir / playbook
122
+ expected_playbook = orig_dir / playbook .replace (".yml" , ".transformed.yml" )
123
+ transformed_playbook = tmp_dir / playbook
124
+
125
+ orig_playbook_content = orig_playbook .read_text ()
126
+ expected_playbook_content = expected_playbook .read_text ()
127
+ transformed_playbook_content = transformed_playbook .read_text ()
128
+
129
+ assert orig_playbook_content != transformed_playbook_content
130
+ assert transformed_playbook_content == expected_playbook_content
0 commit comments