Skip to content

Commit d3c5fe5

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

3 files changed

Lines changed: 92 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: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@
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+
"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+
43+
# Globally set as there might be more future cases where we want to
44+
# classify rules that only apply in strict cases
45+
strict = True
2446

2547

2648
def lint_cfbs_json(filename) -> int:
@@ -117,6 +139,17 @@ def _single_node_checks(filename, lines, node):
117139
f"Deprecation: Promise type '{promise_type}' is deprecated at {filename}:{line}:{column}"
118140
)
119141
return 1
142+
if (
143+
(promise_type not in BUILTIN_PROMISE_TYPES)
144+
and (promise_type not in custom_promise_types)
145+
and strict
146+
):
147+
_highlight_range(node, lines)
148+
print(
149+
f"Error: Undefined promise type '{promise_type}' at {filename}:{line}:{column}"
150+
)
151+
return 1
152+
120153
if node.type == "bundle_block_name":
121154
if _text(node) != _text(node).lower():
122155
_highlight_range(node, lines)
@@ -138,6 +171,7 @@ def _single_node_checks(filename, lines, node):
138171
f"Error: Bundle type must be one of ({', '.join(ALLOWED_BUNDLE_TYPES)}), not '{_text(node)}' at {filename}:{line}:{column}"
139172
)
140173
return 1
174+
141175
return 0
142176

143177

@@ -161,6 +195,26 @@ def _walk(filename, lines, node) -> int:
161195
return errors
162196

163197

198+
def _parse_custom(filename, lines, root_node):
199+
promise_blocks = _find_node_type(filename, lines, root_node, "promise_block_name")
200+
for node in promise_blocks:
201+
custom_promise_types.add(_text(node))
202+
return 0
203+
204+
205+
def _parse_policy_file(filename):
206+
assert os.path.isfile(filename)
207+
PY_LANGUAGE = Language(tscfengine.language())
208+
parser = Parser(PY_LANGUAGE)
209+
210+
with open(filename, "rb") as f:
211+
original_data = f.read()
212+
tree = parser.parse(original_data)
213+
lines = original_data.decode().split("\n")
214+
215+
return tree, lines, original_data
216+
217+
164218
def lint_policy_file(
165219
filename, original_filename=None, original_line=None, snippet=None, prefix=None
166220
):
@@ -177,14 +231,8 @@ def lint_policy_file(
177231
assert snippet and snippet > 0
178232
assert os.path.isfile(filename)
179233
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")
187234

235+
tree, lines, original_data = _parse_policy_file(filename)
188236
root_node = tree.root_node
189237
if root_node.type != "source_file":
190238
if snippet:
@@ -237,6 +285,7 @@ def lint_policy_file(
237285

238286
def lint_folder(folder):
239287
errors = 0
288+
policy_files = []
240289
while folder.endswith(("/.", "/")):
241290
folder = folder[0:-1]
242291
for filename in itertools.chain(
@@ -246,7 +295,21 @@ def lint_folder(folder):
246295
continue
247296
if filename.startswith(".") and not filename.startswith("./"):
248297
continue
249-
errors += lint_single_file(filename)
298+
299+
if filename.endswith((".cf", ".cfengine3", ".cf3", ".cf.sub")):
300+
policy_files.append(filename)
301+
else:
302+
errors += lint_single_file(filename)
303+
304+
# First pass: Gather custom types/bundles/+++
305+
for filename in policy_files:
306+
tree, lines, _ = _parse_policy_file(filename)
307+
if tree.root_node.type == "source_file":
308+
_parse_custom(filename, lines, tree.root_node)
309+
310+
# Second pass: lint all policy files
311+
for filename in policy_files:
312+
errors += lint_policy_file(filename)
250313
return errors
251314

252315

@@ -265,3 +328,12 @@ def lint_single_arg(arg):
265328
return lint_folder(arg)
266329
assert os.path.isfile(arg)
267330
return lint_single_file(arg)
331+
332+
333+
def set_strict(is_strict):
334+
"""
335+
Used to set the global variable 'strict' inside 'lint.py'.
336+
Used for ignoring/handling specific linting rules.
337+
"""
338+
global strict
339+
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)