+import pprint
+
+def lint(sls_yaml, types):
+ """
+ Check the sls_yaml for Salt errors / warnings
+
+ typedb : Nested dictionary of Salt states/functions/parameters (partial)
+ sls_yaml: ruamel.yaml[jinja2].load()'d output for some Salt state file.
+
+ Returns: (errors, warnings)
+ errors: List of (line#, error) tuples
+ warnings: List of (line#, warnings) tuples
+ """
+
+ pp = pprint.PrettyPrinter(indent=4)
+ #pp.pprint(dir(sls_yaml))
+ #pp.pprint(sls_yaml.__dict__)
+
+
+ def lint_parameters(function_name, parameters, parameter_types):
+ """
+ Input:
+ function_name: String of the form "<state_name>.<function>" currently being linted
+ parameters: Map of {paramter_name: parameter_data} items currently being linted
+ parameter_types: Map of {parameter_name: parameter_type} items
+ Output:
+ errors: List of error string
+ warnings: List of warning strings
+ """
+ errors = []
+ warnings = []
+ for parameter in parameters:
+ parameter_name = next(iter(parameter.keys()))
+
+ ## parameter is either just a string or a {'parameter' : data} map, or always a map ?
+
+ if parameter_name in types['globals']:
+ continue
+ elif parameter_name not in parameter_types:
+ s_warning = "Unexpected parameter name {} for {}"
+ warnings.append((parameter.lc.line + 1, s_warning.format(parameter_name, function_name)))
+ else:
+ ## TODO: Add that parameter.values()[0] matches the against parameter_types[parameter] type
+ pass
+ return (errors, warnings)
+
+
+ def lint_function(state_name, function_calls, function_types):
+ """
+ Input:
+ state_name: String of the state_name currently being linted
+ function_calls: Map of {function_name: parameters} items currently being linteds
+ function_types; Map of {function_name: parameter_types} items
+ Output:
+ errors: List of error string
+ warnings: List of warning strings
+ """
+ errors = []
+ warnings = []
+ for function_name, parameters in function_calls.items():
+ if function_name not in function_types:
+ warnings.append((parameters.lc.line+1, "Unexpected function name {}.{}.".format(state_name, function_name)))
+ continue
+ else:
+ (parameter_errors, parameter_warnings) = lint_parameters(state_name + "." + function_name, parameters, function_types[function_name])
+ errors += parameter_errors
+ warnings += parameter_warnings
+ return (errors, warnings)
+
+ ## ------------------------------
+
+ state_types = types['states']
+
+ errors = []
+ warnings = []
+ for label, label_value in sls_yaml.items():
+
+ # It's possible the label_value is just a 'state.function'. See example_sls/two-items.sls
+ if not isinstance(label_value, dict):
+ value = {label_value: []}
+ else:
+ value = label_value
+
+ for state_function_name, function_calls_or_parameters in value.items():
+
+ # state_function_name should be 'state_name' or 'state_name.function_name'
+ # sosf => 'state or state.function'
+ # = [state_name] or [state_name, function_name]
+ sosf = state_function_name.split('.')
+
+ # Verify that sosf[0] is an expected state:
+ state_name = ''
+ if sosf[0] not in state_types:
+ warnings.append((value.lc.line + 1, "Unexpected state name {}.".format(sosf[0])))
+ continue
+ else:
+ state_name = sosf[0]
+
+ # "state" case
+ # function_calls_or_parameters[0] will be the function name
+ # function_call_or_parameters[1:] will be the parameters
+ if len(sosf) == 1:
+
+ if not function_calls_or_parameters:
+ errors.append(label_value.lc.line + 1, "Missing function name in parameter list for state {}.".format(state_name))
+ continue
+
+ function_name = function_calls_or_parameters[0]
+ if function_name not in state_types[state_name]:
+ warnings.append((state_function_name.lc.line + 1, "Unexpected function name {} for {}.".format(function_name, state_name)))
+ continue
+ parameters = function_call_or_parameters[1:]
+ (function_errors, function_warnings) = lint_function(state_name + "." + function_name, parameters, state_types[state_name][function_name])
+ errors += function_errors
+ warnings += function_warnings
+
+ # "state.function" case
+ # function_calls_or_parameters will be a list of parameters
+ elif len(sosf) == 2:
+ function_name = sosf[1]
+ if function_name not in state_types[state_name]:
+
+ # TODO: This gives the line number at the top of the label block which isn't great.
+ # It would be better to give the exact like number of the invalid function
+ warnings.append((sls_yaml.lc.key(label)[0] + 1, "Unexpected function name {} for {}.".format(function_name, state_name)))
+ continue
+
+ # Children should be parameters of "state.function"
+ (parameter_errors, parameter_warnings) = lint_parameters(state_name + "." + function_name, function_calls_or_parameters, state_types[state_name][function_name])
+ errors += parameter_errors
+ warnings += parameter_warnings
+
+ # Anything other than "state" or "state.function" is an error.
+ else:
+ errors.append((value.lc.line + 1, "Expected <state> or <state>.<function>: Too many '.'s in {}".format(state_function_name)))
+ continue
+
+ return(errors, warnings)
+