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+ strict = True
2443
2544
2645def lint_cfbs_json (filename ) -> int :
@@ -117,6 +136,17 @@ def _single_node_checks(filename, lines, node):
117136 f"Deprecation: Promise type '{ promise_type } ' is deprecated at { filename } :{ line } :{ column } "
118137 )
119138 return 1
139+ if (
140+ (promise_type not in BUILTIN_PROMISE_TYPES )
141+ and (promise_type not in custom_promise_types )
142+ and strict
143+ ):
144+ _highlight_range (node , lines )
145+ print (
146+ f"Error: Undefined promise type '{ promise_type } ' at { filename } :{ line } :{ column } "
147+ )
148+ return 1
149+
120150 if node .type == "bundle_block_name" :
121151 if _text (node ) != _text (node ).lower ():
122152 _highlight_range (node , lines )
@@ -138,6 +168,7 @@ def _single_node_checks(filename, lines, node):
138168 f"Error: Bundle type must be one of ({ ', ' .join (ALLOWED_BUNDLE_TYPES )} ), not '{ _text (node )} ' at { filename } :{ line } :{ column } "
139169 )
140170 return 1
171+
141172 return 0
142173
143174
@@ -161,6 +192,26 @@ def _walk(filename, lines, node) -> int:
161192 return errors
162193
163194
195+ def _parse_custom (filename , lines , root_node ):
196+ promise_blocks = _find_node_type (filename , lines , root_node , "promise_block_name" )
197+ for node in promise_blocks :
198+ custom_promise_types .add (_text (node ))
199+ return 0
200+
201+
202+ def _parse_policy_file (filename ):
203+ assert os .path .isfile (filename )
204+ PY_LANGUAGE = Language (tscfengine .language ())
205+ parser = Parser (PY_LANGUAGE )
206+
207+ with open (filename , "rb" ) as f :
208+ original_data = f .read ()
209+ tree = parser .parse (original_data )
210+ lines = original_data .decode ().split ("\n " )
211+
212+ return tree , lines , original_data
213+
214+
164215def lint_policy_file (
165216 filename , original_filename = None , original_line = None , snippet = None , prefix = None
166217):
@@ -177,14 +228,8 @@ def lint_policy_file(
177228 assert snippet and snippet > 0
178229 assert os .path .isfile (filename )
179230 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 " )
187231
232+ tree , lines , original_data = _parse_policy_file (filename )
188233 root_node = tree .root_node
189234 if root_node .type != "source_file" :
190235 if snippet :
@@ -237,6 +282,7 @@ def lint_policy_file(
237282
238283def lint_folder (folder ):
239284 errors = 0
285+ policy_files = []
240286 while folder .endswith (("/." , "/" )):
241287 folder = folder [0 :- 1 ]
242288 for filename in itertools .chain (
@@ -246,7 +292,21 @@ def lint_folder(folder):
246292 continue
247293 if filename .startswith ("." ) and not filename .startswith ("./" ):
248294 continue
249- errors += lint_single_file (filename )
295+
296+ if filename .endswith ((".cf" , ".cfengine3" , ".cf3" , ".cf.sub" )):
297+ policy_files .append (filename )
298+ else :
299+ errors += lint_single_file (filename )
300+
301+ # First pass: Gather custom types/bundles/+++
302+ for filename in policy_files :
303+ tree , lines , _ = _parse_policy_file (filename )
304+ if tree .root_node .type == "source_file" :
305+ _parse_custom (filename , lines , tree .root_node )
306+
307+ # Second pass: lint all policy files
308+ for filename in policy_files :
309+ errors += lint_policy_file (filename )
250310 return errors
251311
252312
@@ -265,3 +325,8 @@ def lint_single_arg(arg):
265325 return lint_folder (arg )
266326 assert os .path .isfile (arg )
267327 return lint_single_file (arg )
328+
329+
330+ def set_strict (is_strict ):
331+ global strict
332+ strict = is_strict
0 commit comments