1818from cfbs .validate import validate_config
1919from cfbs .cfbs_config import CFBSConfig
2020from cfbs .utils import find
21-
22- DEPRECATED_PROMISE_TYPES = ["defaults" , "guest_environments" ]
23- ALLOWED_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- }
21+ from cfengine_cli .policy_language import (
22+ DEPRECATED_PROMISE_TYPES ,
23+ ALLOWED_BUNDLE_TYPES ,
24+ BUILTIN_PROMISE_TYPES ,
25+ BUILTIN_FUNCTIONS ,
26+ )
5627
5728
5829def lint_cfbs_json (filename ) -> int :
@@ -129,7 +100,7 @@ def _find_nodes(filename, lines, node):
129100 return matches
130101
131102
132- def _single_node_checks (filename , lines , node , custom_promise_types , strict ):
103+ def _single_node_checks (filename , lines , node , user_definition , strict ):
133104 """Things which can be checked by only looking at one node,
134105 not needing to recurse into children."""
135106 line = node .range .start_point [0 ] + 1
@@ -150,7 +121,12 @@ def _single_node_checks(filename, lines, node, custom_promise_types, strict):
150121 )
151122 return 1
152123 if strict and (
153- (promise_type not in BUILTIN_PROMISE_TYPES .union (custom_promise_types ))
124+ (
125+ promise_type
126+ not in BUILTIN_PROMISE_TYPES .union (
127+ user_definition .get ("custom_promise_types" , set ())
128+ )
129+ )
154130 ):
155131 _highlight_range (node , lines )
156132 print (
@@ -179,13 +155,25 @@ def _single_node_checks(filename, lines, node, custom_promise_types, strict):
179155 f"Error: Bundle type must be one of ({ ', ' .join (ALLOWED_BUNDLE_TYPES )} ), not '{ _text (node )} ' at { filename } :{ line } :{ column } "
180156 )
181157 return 1
182-
158+ if node .type == "calling_identifier" :
159+ if strict and (
160+ _text (node )
161+ not in BUILTIN_FUNCTIONS .union (
162+ user_definition .get ("all_bundle_names" , set ()),
163+ user_definition .get ("all_body_names" , set ()),
164+ )
165+ ):
166+ _highlight_range (node , lines )
167+ print (
168+ f"Error: Call to unknown function / bundle / body '{ _text (node )} ' at at { filename } :{ line } :{ column } "
169+ )
170+ return 1
183171 return 0
184172
185173
186- def _walk (filename , lines , node , custom_promise_types = None , strict = True ) -> int :
187- if custom_promise_types is None :
188- custom_promise_types = set ()
174+ def _walk (filename , lines , node , user_definition = None , strict = True ) -> int :
175+ if user_definition is None :
176+ user_definition = {}
189177
190178 error_nodes = _find_node_type (filename , lines , node , "ERROR" )
191179 if error_nodes :
@@ -201,18 +189,21 @@ def _walk(filename, lines, node, custom_promise_types=None, strict=True) -> int:
201189
202190 errors = 0
203191 for node in _find_nodes (filename , lines , node ):
204- errors += _single_node_checks (
205- filename , lines , node , custom_promise_types , strict
206- )
192+ errors += _single_node_checks (filename , lines , node , user_definition , strict )
207193
208194 return errors
209195
210196
211- def _parse_custom_types (filename , lines , root_node ):
212- ret = set ()
197+ def _parse_user_definition (filename , lines , root_node ):
213198 promise_blocks = _find_node_type (filename , lines , root_node , "promise_block_name" )
214- ret .update (_text (x ) for x in promise_blocks )
215- return ret
199+ bundle_blocks = _find_node_type (filename , lines , root_node , "bundle_block_name" )
200+ body_blocks = _find_node_type (filename , lines , root_node , "body_block_name" )
201+
202+ return {
203+ "custom_promise_types" : {_text (x ) for x in promise_blocks },
204+ "all_bundle_names" : {_text (x ) for x in bundle_blocks },
205+ "all_body_names" : {_text (x ) for x in body_blocks },
206+ }
216207
217208
218209def _parse_policy_file (filename ):
@@ -234,7 +225,7 @@ def lint_policy_file(
234225 original_line = None ,
235226 snippet = None ,
236227 prefix = None ,
237- custom_promise_types = None ,
228+ user_definition = None ,
238229 strict = True ,
239230):
240231 assert original_filename is None or type (original_filename ) is str
@@ -251,8 +242,8 @@ def lint_policy_file(
251242 assert os .path .isfile (filename )
252243 assert filename .endswith ((".cf" , ".cfengine3" , ".cf3" , ".cf.sub" ))
253244
254- if custom_promise_types is None :
255- custom_promise_types = set ()
245+ if user_definition is None :
246+ user_definition = {}
256247
257248 tree , lines , original_data = _parse_policy_file (filename )
258249 root_node = tree .root_node
@@ -284,7 +275,7 @@ def lint_policy_file(
284275 else :
285276 print (f"Error: Empty policy file '{ filename } '" )
286277 errors += 1
287- errors += _walk (filename , lines , root_node , custom_promise_types , strict )
278+ errors += _walk (filename , lines , root_node , user_definition , strict )
288279 if prefix :
289280 print (prefix , end = "" )
290281 if errors == 0 :
@@ -323,34 +314,33 @@ def lint_folder(folder, strict=True):
323314 else :
324315 errors += lint_single_file (filename )
325316
326- custom_promise_types = set ()
317+ user_definition = {}
327318
328319 # First pass: Gather custom types
329320 for filename in policy_files if strict else []:
330321 tree , lines , _ = _parse_policy_file (filename )
331322 if tree .root_node .type == "source_file" :
332- custom_promise_types .update (
333- _parse_custom_types (filename , lines , tree .root_node )
334- )
323+ for key , val in _parse_user_definition (
324+ filename , lines , tree .root_node
325+ ).items ():
326+ user_definition [key ] = user_definition .get (key , set ()).union (val )
335327
336328 # Second pass: lint all policy files
337329 for filename in policy_files :
338330 errors += lint_policy_file (
339- filename , custom_promise_types = custom_promise_types , strict = strict
331+ filename , user_definition = user_definition , strict = strict
340332 )
341333 return errors
342334
343335
344- def lint_single_file (file , custom_promise_types = None , strict = True ):
336+ def lint_single_file (file , user_definition = None , strict = True ):
345337 assert os .path .isfile (file )
346338 if file .endswith ("/cfbs.json" ):
347339 return lint_cfbs_json (file )
348340 if file .endswith (".json" ):
349341 return lint_json (file )
350342 assert file .endswith (".cf" )
351- return lint_policy_file (
352- file , custom_promise_types = custom_promise_types , strict = strict
353- )
343+ return lint_policy_file (file , user_definition = user_definition , strict = strict )
354344
355345
356346def lint_single_arg (arg , strict = True ):
0 commit comments