diff --git a/src/cfengine_cli/lint.py b/src/cfengine_cli/lint.py index ad4d999..2c36814 100644 --- a/src/cfengine_cli/lint.py +++ b/src/cfengine_cli/lint.py @@ -52,7 +52,14 @@ "string", } PROMISE_BLOCK_ATTRIBUTES = ("path", "interpreter") + IMPLIES_BUNDLE = {"usebundle", "servicebundle", "service_bundle"} +IMPLIES_BODY = {"copy_from", "action"} +# Generally, IMPLIES_BUNDLE and IMPLIES_BODY might not be necessary +# in the future, when we're using syntax-description.json we will +# know if we expect a bundle or body (based on both promise type and attribute name) +# so "guessing" based on only attribute name can be dropped. + KNOWN_FAULTY_FUNCTION_DEFS = {"regex_replace", "peers"} # Generally, we don't want to allow creating bodies / bundles with the same # name as a built in function, as it can make things more confusing @@ -652,19 +659,18 @@ def _lint_node( f"Error: Call to bundle '{name}' inside custom promise: '{state.promise_type}' {location}" ) return 1 - if state.strict and ( - qualified_name not in state.bundles - and ( - state.attribute_name in IMPLIES_BUNDLE - or qualified_name not in state.bodies - ) - and name not in syntax_data.BUILTIN_FUNCTIONS - ): - _highlight_range(node, lines) - print( - f"Error: Call to unknown function / bundle / body '{name}' {location}" + if state.strict and name not in syntax_data.BUILTIN_FUNCTIONS: + allowed_in_bundles = state.attribute_name not in IMPLIES_BODY + allowed_in_bodies = state.attribute_name not in IMPLIES_BUNDLE + found = (allowed_in_bundles and qualified_name in state.bundles) or ( + allowed_in_bodies and qualified_name in state.bodies ) - return 1 + if not found: + _highlight_range(node, lines) + print( + f"Error: Call to unknown function / bundle / body '{name}' {location}" + ) + return 1 if ( name not in syntax_data.BUILTIN_FUNCTIONS and state.promise_type == "vars" @@ -757,7 +763,7 @@ def _lint_node( return 1 qualified_name = _qualify(call, state.namespace) - if qualified_name in state.bundles: + if qualified_name in state.bundles and state.attribute_name not in IMPLIES_BODY: definitions = state.bundles[qualified_name] valid_counts = {len(d.get("parameters", [])) for d in definitions} if len(args) not in valid_counts: diff --git a/tests/lint/018_implies_body.cf b/tests/lint/018_implies_body.cf new file mode 100644 index 0000000..8a3c3c2 --- /dev/null +++ b/tests/lint/018_implies_body.cf @@ -0,0 +1,12 @@ +body copy_from mycopy(from, server) +{ + source => "$(from)"; + servers => { "$(server)" }; +} + +bundle agent main +{ + files: + "/tmp/test" + copy_from => mycopy("/src", "host1"); +} diff --git a/tests/lint/018_implies_body.expected.txt b/tests/lint/018_implies_body.expected.txt new file mode 100644 index 0000000..4b31700 --- /dev/null +++ b/tests/lint/018_implies_body.expected.txt @@ -0,0 +1,17 @@ + + "/tmp/test1" + copy_from => helper("oops"); + ^----^ +Error: Call to unknown function / bundle / body 'helper' at tests/lint/018_implies_body.x.cf:17:20 + + "/tmp/test2" + copy_from => unknown_name("oops"); + ^----------^ +Error: Call to unknown function / bundle / body 'unknown_name' at tests/lint/018_implies_body.x.cf:19:20 + + "/tmp/test3" + copy_from => mycopy("/src"); + ^------------^ +Error: Expected 2 arguments, received 1 for body 'mycopy' at tests/lint/018_implies_body.x.cf:21:20 +FAIL: tests/lint/018_implies_body.x.cf (3 errors) +Failure, 3 errors in total. diff --git a/tests/lint/018_implies_body.x.cf b/tests/lint/018_implies_body.x.cf new file mode 100644 index 0000000..8d2ab2c --- /dev/null +++ b/tests/lint/018_implies_body.x.cf @@ -0,0 +1,22 @@ +body copy_from mycopy(from, server) +{ + source => "$(from)"; + servers => { "$(server)" }; +} + +bundle agent helper(arg) +{ + reports: + "$(arg)"; +} + +bundle agent main +{ + files: + "/tmp/test1" + copy_from => helper("oops"); + "/tmp/test2" + copy_from => unknown_name("oops"); + "/tmp/test3" + copy_from => mycopy("/src"); +}