2121
2222DEPRECATED_PROMISE_TYPES = ["defaults" , "guest_environments" ]
2323ALLOWED_BUNDLE_TYPES = ["agent" , "common" , "monitor" , "server" , "edit_line" , "edit_xml" ]
24+ BUILTIN_PROMISE_TYPES = {
25+ "access" ,
26+ "build_xpath" ,
27+ "classes" ,
28+ "commands" ,
29+ "databases" ,
30+ "defaults" ,
31+ "delete_attribute" ,
32+ "delete_lines" ,
33+ "delete_text" ,
34+ "delete_tree" ,
35+ "field_edits" ,
36+ "files" ,
37+ "guest_environments" ,
38+ "insert_lines" ,
39+ "insert_text" ,
40+ "insert_tree" ,
41+ "measurements" ,
42+ "meta" ,
43+ "methods" ,
44+ "packages" ,
45+ "processes" ,
46+ "replace_patterns" ,
47+ "reports" ,
48+ "roles" ,
49+ "services" ,
50+ "set_attribute" ,
51+ "set_text" ,
52+ "storage" ,
53+ "users" ,
54+ "vars" ,
55+ }
56+
57+ custom_promise_types = set ()
58+
59+ # Globally set as there might be more future cases where we want to
60+ # classify rules that only apply in strict cases
61+ strict = True
2462
2563
2664def lint_cfbs_json (filename ) -> int :
@@ -117,6 +155,17 @@ def _single_node_checks(filename, lines, node):
117155 f"Deprecation: Promise type '{ promise_type } ' is deprecated at { filename } :{ line } :{ column } "
118156 )
119157 return 1
158+ if (
159+ (promise_type not in BUILTIN_PROMISE_TYPES )
160+ and (promise_type not in custom_promise_types )
161+ and strict
162+ ):
163+ _highlight_range (node , lines )
164+ print (
165+ f"Error: Undefined promise type '{ promise_type } ' at { filename } :{ line } :{ column } "
166+ )
167+ return 1
168+
120169 if node .type == "bundle_block_name" :
121170 if _text (node ) != _text (node ).lower ():
122171 _highlight_range (node , lines )
@@ -138,6 +187,7 @@ def _single_node_checks(filename, lines, node):
138187 f"Error: Bundle type must be one of ({ ', ' .join (ALLOWED_BUNDLE_TYPES )} ), not '{ _text (node )} ' at { filename } :{ line } :{ column } "
139188 )
140189 return 1
190+
141191 return 0
142192
143193
@@ -161,6 +211,26 @@ def _walk(filename, lines, node) -> int:
161211 return errors
162212
163213
214+ def _parse_custom (filename , lines , root_node ):
215+ promise_blocks = _find_node_type (filename , lines , root_node , "promise_block_name" )
216+ for node in promise_blocks :
217+ custom_promise_types .add (_text (node ))
218+ return 0
219+
220+
221+ def _parse_policy_file (filename ):
222+ assert os .path .isfile (filename )
223+ PY_LANGUAGE = Language (tscfengine .language ())
224+ parser = Parser (PY_LANGUAGE )
225+
226+ with open (filename , "rb" ) as f :
227+ original_data = f .read ()
228+ tree = parser .parse (original_data )
229+ lines = original_data .decode ().split ("\n " )
230+
231+ return tree , lines , original_data
232+
233+
164234def lint_policy_file (
165235 filename , original_filename = None , original_line = None , snippet = None , prefix = None
166236):
@@ -177,14 +247,8 @@ def lint_policy_file(
177247 assert snippet and snippet > 0
178248 assert os .path .isfile (filename )
179249 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 " )
187250
251+ tree , lines , original_data = _parse_policy_file (filename )
188252 root_node = tree .root_node
189253 if root_node .type != "source_file" :
190254 if snippet :
@@ -237,6 +301,7 @@ def lint_policy_file(
237301
238302def lint_folder (folder ):
239303 errors = 0
304+ policy_files = []
240305 while folder .endswith (("/." , "/" )):
241306 folder = folder [0 :- 1 ]
242307 for filename in itertools .chain (
@@ -246,7 +311,21 @@ def lint_folder(folder):
246311 continue
247312 if filename .startswith ("." ) and not filename .startswith ("./" ):
248313 continue
249- errors += lint_single_file (filename )
314+
315+ if filename .endswith ((".cf" , ".cfengine3" , ".cf3" , ".cf.sub" )):
316+ policy_files .append (filename )
317+ else :
318+ errors += lint_single_file (filename )
319+
320+ # First pass: Gather custom types/bundles/+++
321+ for filename in policy_files :
322+ tree , lines , _ = _parse_policy_file (filename )
323+ if tree .root_node .type == "source_file" :
324+ _parse_custom (filename , lines , tree .root_node )
325+
326+ # Second pass: lint all policy files
327+ for filename in policy_files :
328+ errors += lint_policy_file (filename )
250329 return errors
251330
252331
@@ -265,3 +344,12 @@ def lint_single_arg(arg):
265344 return lint_folder (arg )
266345 assert os .path .isfile (arg )
267346 return lint_single_file (arg )
347+
348+
349+ def set_strict (is_strict ):
350+ """
351+ Used to set the global variable 'strict' inside 'lint.py'.
352+ Used for ignoring/handling specific linting rules.
353+ """
354+ global strict
355+ strict = is_strict
0 commit comments