Skip to content

Commit c87c3c9

Browse files
ENT-13073: Added linter errors for unknown promisetypes
Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
1 parent 7b58304 commit c87c3c9

3 files changed

Lines changed: 108 additions & 13 deletions

File tree

src/cfengine_cli/commands.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import json
55
from cfengine_cli.profile import profile_cfengine, generate_callstack
66
from cfengine_cli.dev import dispatch_dev_subcommand
7-
from cfengine_cli.lint import lint_single_arg, lint_folder
7+
from cfengine_cli.lint import lint_folder, lint_single_arg, set_strict
88
from cfengine_cli.shell import user_command
99
from cfengine_cli.paths import bin
1010
from cfengine_cli.version import cfengine_cli_version_string
@@ -94,7 +94,8 @@ def format(names, line_length) -> int:
9494
return 0
9595

9696

97-
def _lint(files) -> int:
97+
def _lint(files, strict) -> int:
98+
set_strict(strict)
9899

99100
if not files:
100101
return lint_folder(".")
@@ -107,8 +108,8 @@ def _lint(files) -> int:
107108
return errors
108109

109110

110-
def lint(files) -> int:
111-
errors = _lint(files)
111+
def lint(files, strict) -> int:
112+
errors = _lint(files, strict)
112113
if errors == 0:
113114
print("Success, no errors found.")
114115
else:

src/cfengine_cli/lint.py

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,44 @@
2121

2222
DEPRECATED_PROMISE_TYPES = ["defaults", "guest_environments"]
2323
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+
}
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

2664
def 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+
164234
def 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

238302
def 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

src/cfengine_cli/main.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ def _get_arg_parser():
5252
"lint",
5353
help="Look for syntax errors and other simple mistakes",
5454
)
55+
lnt.add_argument(
56+
"--strict",
57+
type=str,
58+
default="yes",
59+
help="Strict mode. Default=yes, checks for custom promisetypes",
60+
)
5561
lnt.add_argument("files", nargs="*", help="Files to format")
5662
subp.add_parser(
5763
"report",
@@ -132,7 +138,7 @@ def run_command_with_args(args) -> int:
132138
if args.command == "format":
133139
return commands.format(args.files, args.line_length)
134140
if args.command == "lint":
135-
return commands.lint(args.files)
141+
return commands.lint(args.files, args.strict.lower() in ("y", "ye", "yes"))
136142
if args.command == "report":
137143
return commands.report()
138144
if args.command == "run":

0 commit comments

Comments
 (0)