4 from salt
.state
import STATE_INTERNAL_KEYWORDS
8 Check the sls_yaml for Salt errors / warnings
10 sls_yaml: ruamel.yaml[jinja2].load()'d output for some Salt state file.
12 Returns: (errors, warnings)
13 errors: List of (line#, error) tuples
14 warnings: List of (line#, warnings) tuples
19 def lint_parameters(state_name
, function_name
, actual_parameters
, state_module
):
27 allowable_parameters
= inspect
.signature(vars(state_module
)[function_name
]).parameters
28 except Exception as e
:
29 s_error
= "Unexpected error looking up {}.{} parameters: {}."
30 perrors
.append((1, s_error
.format(state_name
, function_name
, e
)))
31 return (perrors
, pwarnings
)
33 for a_parameter
in actual_parameters
:
35 # TODO: A comment explaining this line...
36 parameter_name
= next(iter(a_parameter
.keys()))
38 # Accept any of the global state arguments
39 # (maybe ignore 'state' and 'fun' ??)
40 if parameter_name
in STATE_INTERNAL_KEYWORDS
:
42 elif parameter_name
not in allowable_parameters
:
43 s_warning
= "Unexpected parameter '{}' for function '{}.{}'."
44 pwarnings
.append((a_parameter
.lc
.line
+ 1, s_warning
.format(parameter_name
, state_name
, function_name
)))
46 # If there are duplicate parameters in the state, it looks like salt will use the last seen parameter,
47 # but it looks like that's undefined behavior, but its more likely some copypasta error on your part.
48 if parameter_name
in seen_parameters
:
49 s_error
= "Duplicate parameter '{}' for function '{}.{}'."
50 perrors
.append((a_parameter
.lc
.line
+ 1, s_error
.format(parameter_name
, state_name
, function_name
)))
52 seen_parameters
.append(parameter_name
)
54 return (perrors
, pwarnings
)
57 ## ------------------------------
61 for label
, label_value
in sls_yaml
.items():
63 # It's possible the label_value is just a 'state.function'. See example_sls/two-items.sls
64 if not isinstance(label_value
, dict):
65 value
= {label_value
: []}
69 for state_function_name
, function_calls_or_parameters
in value
.items():
71 # state_function_name should be 'state_name' or 'state_name.function_name'
72 # sosf => 'state or state.function'
73 # = [state_name] or [state_name, function_name]
74 sosf
= state_function_name
.split('.')
76 # Load the state module if needed.
77 # If it's already been loaded, use the cached version
79 if sosf
[0] in module_cache
:
81 state_mod
= module_cache
[state_name
]
84 state_mod
= importlib
.import_module('salt.states.' + sosf
[0])
85 except ModuleNotFoundError
:
86 warnings
.append((value
.lc
.line
+ 1, "Unexpected state name {}.".format(sosf
[0])))
90 module_cache
[state_name
] = state_mod
93 # function_calls_or_parameters[0] will be the function name
94 # function_calls_or_parameters[1:] will be the parameters
97 if not function_calls_or_parameters
:
98 errors
.append(label_value
.lc
.line
+ 1, "Missing function name in parameter list for state {}.".format(state_name
))
101 function_name
= function_calls_or_parameters
[0]
102 parameters
= function_calls_or_parameters
[1:]
104 if function_name
not in vars(state_mod
):
105 warnings
.append((state_function_name
.lc
.line
+ 1, "Unexpected function name '{}' for state {}.".format(function_name
, state_name
)))
108 (perrors
, pwarnings
) = lint_parameters(state_name
, function_name
, parameters
, state_mod
)
110 warnings
+= pwarnings
112 # "state.function" case
113 # function_calls_or_parameters will be a list of parameters
115 function_name
= sosf
[1]
116 if function_name
not in vars(state_mod
):
118 # TODO: This gives the line number at the top of the label block which isn't great.
119 # It would be better to give the exact like number of the invalid function
120 warnings
.append((sls_yaml
.lc
.key(label
)[0] + 1, "Unexpected function name '{}' for state {}.".format(function_name
, state_name
)))
123 parameters
= function_calls_or_parameters
124 (perrors
, pwarnings
) = lint_parameters(state_name
, function_name
, parameters
, state_mod
)
126 warnings
+= pwarnings
128 # Anything other than "state" or "state.function" is an error.
130 errors
.append((value
.lc
.line
+ 1, "Expected <state> or <state>.<function>: Too many '.'s in {}.".format(state_function_name
)))
133 return(errors
, warnings
)