2121
2222DEPRECATED_PROMISE_TYPES = ["defaults" , "guest_environments" ]
2323ALLOWED_BUNDLE_TYPES = ["agent" , "common" , "monitor" , "server" , "edit_line" , "edit_xml" ]
24+ BUILTIN_PROMISE_TYPES = {
25+ "commands" ,
26+ "databases" ,
27+ "guest_environments" ,
28+ "files" ,
29+ "methods" ,
30+ "packages" ,
31+ "processes" ,
32+ "services" ,
33+ "storage" ,
34+ "users" ,
35+ "classes" ,
36+ "defaults" ,
37+ "meta" ,
38+ "reports" ,
39+ "vars" ,
40+ }
41+ custom_promise_types = set ()
42+
43+ # Globally set as there might be more future cases where we want to
44+ # classify rules that only apply in strict cases
45+ strict = True
2446
2547
2648def lint_cfbs_json (filename ) -> int :
@@ -117,6 +139,17 @@ def _single_node_checks(filename, lines, node):
117139 f"Deprecation: Promise type '{ promise_type } ' is deprecated at { filename } :{ line } :{ column } "
118140 )
119141 return 1
142+ if (
143+ (promise_type not in BUILTIN_PROMISE_TYPES )
144+ and (promise_type not in custom_promise_types )
145+ and strict
146+ ):
147+ _highlight_range (node , lines )
148+ print (
149+ f"Error: Undefined promise type '{ promise_type } ' at { filename } :{ line } :{ column } "
150+ )
151+ return 1
152+
120153 if node .type == "bundle_block_name" :
121154 if _text (node ) != _text (node ).lower ():
122155 _highlight_range (node , lines )
@@ -138,6 +171,7 @@ def _single_node_checks(filename, lines, node):
138171 f"Error: Bundle type must be one of ({ ', ' .join (ALLOWED_BUNDLE_TYPES )} ), not '{ _text (node )} ' at { filename } :{ line } :{ column } "
139172 )
140173 return 1
174+
141175 return 0
142176
143177
@@ -161,6 +195,26 @@ def _walk(filename, lines, node) -> int:
161195 return errors
162196
163197
198+ def _parse_custom (filename , lines , root_node ):
199+ promise_blocks = _find_node_type (filename , lines , root_node , "promise_block_name" )
200+ for node in promise_blocks :
201+ custom_promise_types .add (_text (node ))
202+ return 0
203+
204+
205+ def _parse_policy_file (filename ):
206+ assert os .path .isfile (filename )
207+ PY_LANGUAGE = Language (tscfengine .language ())
208+ parser = Parser (PY_LANGUAGE )
209+
210+ with open (filename , "rb" ) as f :
211+ original_data = f .read ()
212+ tree = parser .parse (original_data )
213+ lines = original_data .decode ().split ("\n " )
214+
215+ return tree , lines , original_data
216+
217+
164218def lint_policy_file (
165219 filename , original_filename = None , original_line = None , snippet = None , prefix = None
166220):
@@ -177,14 +231,8 @@ def lint_policy_file(
177231 assert snippet and snippet > 0
178232 assert os .path .isfile (filename )
179233 assert filename .endswith ((".cf" , ".cfengine3" , ".cf3" , ".cf.sub" ))
180- PY_LANGUAGE = Language (tscfengine .language ())
181- parser = Parser (PY_LANGUAGE )
182-
183- with open (filename , "rb" ) as f :
184- original_data = f .read ()
185- tree = parser .parse (original_data )
186- lines = original_data .decode ().split ("\n " )
187234
235+ tree , lines , original_data = _parse_policy_file (filename )
188236 root_node = tree .root_node
189237 if root_node .type != "source_file" :
190238 if snippet :
@@ -237,6 +285,7 @@ def lint_policy_file(
237285
238286def lint_folder (folder ):
239287 errors = 0
288+ policy_files = []
240289 while folder .endswith (("/." , "/" )):
241290 folder = folder [0 :- 1 ]
242291 for filename in itertools .chain (
@@ -246,7 +295,21 @@ def lint_folder(folder):
246295 continue
247296 if filename .startswith ("." ) and not filename .startswith ("./" ):
248297 continue
249- errors += lint_single_file (filename )
298+
299+ if filename .endswith ((".cf" , ".cfengine3" , ".cf3" , ".cf.sub" )):
300+ policy_files .append (filename )
301+ else :
302+ errors += lint_single_file (filename )
303+
304+ # First pass: Gather custom types/bundles/+++
305+ for filename in policy_files :
306+ tree , lines , _ = _parse_policy_file (filename )
307+ if tree .root_node .type == "source_file" :
308+ _parse_custom (filename , lines , tree .root_node )
309+
310+ # Second pass: lint all policy files
311+ for filename in policy_files :
312+ errors += lint_policy_file (filename )
250313 return errors
251314
252315
@@ -265,3 +328,12 @@ def lint_single_arg(arg):
265328 return lint_folder (arg )
266329 assert os .path .isfile (arg )
267330 return lint_single_file (arg )
331+
332+
333+ def set_strict (is_strict ):
334+ """
335+ Used to set the global variable 'strict' inside 'lint.py'.
336+ Used for ignoring/handling specific linting rules.
337+ """
338+ global strict
339+ strict = is_strict
0 commit comments