3
3
4
4
import logging
5
5
import sys
6
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING , Any
7
7
8
+ from ansiblelint .app import get_app
8
9
from ansiblelint .errors import MatchError
9
10
from ansiblelint .file_utils import Lintable
10
11
from ansiblelint .rules import AnsibleLintRule
11
12
from ansiblelint .schemas .__main__ import JSON_SCHEMAS
12
13
from ansiblelint .schemas .main import validate_file_schema
14
+ from ansiblelint .text import has_jinja
13
15
14
16
if TYPE_CHECKING :
15
17
from ansiblelint .utils import Task
47
49
},
48
50
}
49
51
52
+ FIELD_CHECKS = {
53
+ "become_method" : get_app ().runtime .plugins .become .keys (), # pylint: disable=no-member
54
+ }
55
+
50
56
51
57
class ValidateSchemaRule (AnsibleLintRule ):
52
58
"""Perform JSON Schema Validation for known lintable kinds."""
@@ -75,12 +81,49 @@ class ValidateSchemaRule(AnsibleLintRule):
75
81
"schema[vars]" : "" ,
76
82
}
77
83
84
+ become_method_msg = f"'become_method' must be one of the currently installed plugins: { ', ' .join (FIELD_CHECKS ['become_method' ])} "
85
+
86
+ def matchplay (self , file : Lintable , data : dict [str , Any ]) -> list [MatchError ]:
87
+ """Return matches found for a specific playbook."""
88
+ results : list [MatchError ] = []
89
+ if not data or file .kind not in ("tasks" , "handlers" , "playbook" ):
90
+ return results
91
+ # check at play level
92
+ for key , value in FIELD_CHECKS .items ():
93
+ if key in data :
94
+ plugin_value = data .get (key , None )
95
+ if not has_jinja (plugin_value ) and plugin_value not in value :
96
+ results .append (
97
+ MatchError (
98
+ message = self .become_method_msg ,
99
+ lintable = file or Lintable ("" ),
100
+ rule = ValidateSchemaRule (),
101
+ details = ValidateSchemaRule .description ,
102
+ tag = f"schema[{ file .kind } ]" ,
103
+ ),
104
+ )
105
+
106
+ return results
107
+
78
108
def matchtask (
79
109
self ,
80
110
task : Task ,
81
111
file : Lintable | None = None ,
82
112
) -> bool | str | MatchError | list [MatchError ]:
83
113
result = []
114
+ for key , value in FIELD_CHECKS .items ():
115
+ if key in task .raw_task :
116
+ plugin_value = task .raw_task .get (key , None )
117
+ if not has_jinja (plugin_value ) and plugin_value not in value :
118
+ result .append (
119
+ MatchError (
120
+ message = self .become_method_msg ,
121
+ lintable = file or Lintable ("" ),
122
+ rule = ValidateSchemaRule (),
123
+ details = ValidateSchemaRule .description ,
124
+ tag = f"schema[{ file .kind } ]" , # type: ignore[union-attr]
125
+ ),
126
+ )
84
127
for key in pre_checks ["task" ]:
85
128
if key in task .raw_task :
86
129
msg = pre_checks ["task" ][key ]["msg" ]
@@ -98,9 +141,9 @@ def matchtask(
98
141
99
142
def matchyaml (self , file : Lintable ) -> list [MatchError ]:
100
143
"""Return JSON validation errors found as a list of MatchError(s)."""
101
- result = []
144
+ result : list [ MatchError ] = []
102
145
if file .kind not in JSON_SCHEMAS :
103
- return []
146
+ return result
104
147
105
148
errors = validate_file_schema (file )
106
149
if errors :
@@ -120,6 +163,9 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
120
163
tag = f"schema[{ file .kind } ]" ,
121
164
),
122
165
)
166
+
167
+ if not result :
168
+ result = super ().matchyaml (file )
123
169
return result
124
170
125
171
@@ -252,6 +298,21 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
252
298
[],
253
299
id = "rulebook2" ,
254
300
),
301
+ pytest .param (
302
+ "examples/playbooks/rule-schema-become-method-pass.yml" ,
303
+ "playbook" ,
304
+ [],
305
+ id = "playbook" ,
306
+ ),
307
+ pytest .param (
308
+ "examples/playbooks/rule-schema-become-method-fail.yml" ,
309
+ "playbook" ,
310
+ [
311
+ "'become_method' must be one of the currently installed plugins" ,
312
+ "'become_method' must be one of the currently installed plugins" ,
313
+ ],
314
+ id = "playbook2" ,
315
+ ),
255
316
),
256
317
)
257
318
def test_schema (file : str , expected_kind : str , expected : list [str ]) -> None :
0 commit comments