reclass module to check params and classes
Change-Id: I36bb0c29f5eec3f32a5da8da11cb6fc8a5377b7f
diff --git a/_modules/reclass.py b/_modules/reclass.py
index e53aecb..eace0bf 100644
--- a/_modules/reclass.py
+++ b/_modules/reclass.py
@@ -14,6 +14,7 @@
import sys
import six
import yaml
+import re
from urlparse import urlparse
from reclass import get_storage, output
@@ -21,6 +22,8 @@
from reclass.core import Core
from reclass.config import find_and_read_configfile
from string import Template
+from reclass.errors import ReclassException
+
LOG = logging.getLogger(__name__)
@@ -33,6 +36,83 @@
return 'reclass'
+def _deps(ret_classes=True, ret_errors=False):
+ '''
+ Returns classes if ret_classes=True, else returns soft_params if ret_classes=False
+ '''
+ defaults = find_and_read_configfile()
+ path = defaults.get('inventory_base_uri')
+ classes = {}
+ soft_params = {}
+ errors = []
+
+ # find classes
+ for root, dirs, files in os.walk(path):
+ if 'init.yml' in files:
+ class_file = root + '/' + 'init.yml'
+ class_name = class_file.replace(path, '')[:-9].replace('/', '.')
+ classes[class_name] = {'file': class_file}
+
+ for f in files:
+ if f.endswith('.yml') and f != 'init.yml':
+ class_file = root + '/' + f
+ class_name = class_file.replace(path, '')[:-4].replace('/', '.')
+ classes[class_name] = {'file': class_file}
+
+ # read classes
+ for class_name, params in classes.items():
+ with open(params['file'], 'r') as f:
+ # read raw data
+ raw = f.read()
+ pr = re.findall('\${_param:(.*?)}', raw)
+ if pr:
+ params['params_required'] = list(set(pr))
+
+ # load yaml
+ try:
+ data = yaml.load(raw)
+ except yaml.scanner.ScannerError as e:
+ errors.append(params['file'] + ' ' + str(e))
+ pass
+
+ if type(data) == dict:
+ if data.get('classes'):
+ params['includes'] = data.get('classes', [])
+ if data.get('parameters') and data['parameters'].get('_param'):
+ params['params_created'] = data['parameters']['_param']
+
+ if not(data.get('classes') or data.get('parameters')):
+ errors.append(params['file'] + ' ' + 'file missing classes and parameters')
+ else:
+ errors.append(params['file'] + ' ' + 'is not valid yaml')
+
+ if ret_classes:
+ return classes
+ elif ret_errors:
+ return errors
+
+ # find parameters and its usage
+ for class_name, params in classes.items():
+ for pn, pv in params.get('params_created', {}).items():
+ # create param if missing
+ if pn not in soft_params:
+ soft_params[pn] = {'created_at': {}, 'required_at': []}
+
+ # add created_at
+ if class_name not in soft_params[pn]['created_at']:
+ soft_params[pn]['created_at'][class_name] = pv
+
+ for pn in params.get('params_required', []):
+ # create param if missing
+ if pn not in soft_params:
+ soft_params[pn] = {'created_at': {}, 'required_at': []}
+
+ # add created_at
+ soft_params[pn]['required_at'].append(class_name)
+
+ return soft_params
+
+
def _get_nodes_dir():
defaults = find_and_read_configfile()
return os.path.join(defaults.get('inventory_base_uri'), 'nodes')
@@ -87,6 +167,87 @@
return node_meta
+def validate_yaml_syntax():
+ '''
+ Returns list of yaml files with syntax errors
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt '*' reclass.validate_yaml_syntax
+ '''
+ errors = _deps(ret_classes=False, ret_errors=True)
+ if errors:
+ ret = {'Errors': errors}
+ return ret
+
+
+def soft_meta_list():
+ '''
+ Returns params list
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt '*' reclass.soft_meta_list
+ '''
+ return _deps(ret_classes=False)
+
+
+def class_list():
+ '''
+ Returns classes list
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt '*' reclass.class_list
+ '''
+ return _deps(ret_classes=True)
+
+
+def soft_meta_get(name):
+ '''
+ :param name: expects the following format: apt_mk_version
+
+ Returns detail of the params
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt '*' reclass.soft_meta_get apt_mk_version
+ '''
+ soft_params = _deps(ret_classes=False)
+
+ if name in soft_params:
+ return {name: soft_params.get(name)}
+ else:
+ return {'Error': 'No param {0} found'.format(name)}
+
+def class_get(name):
+ '''
+ :param name: expects the following format classes.system.linux.repo
+
+ Returns detail data of the class
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt '*' reclass.class_get classes.system.linux.repo
+ '''
+ classes = _deps(ret_classes=True)
+ tmp_name = '.' + name
+ if tmp_name in classes:
+ return {name: classes.get(tmp_name)}
+ else:
+ return {'Error': 'No class {0} found'.format(name)}
+
+
+
def node_create(name, path=None, cluster="default", environment="prd", classes=None, parameters=None, **kwargs):
'''
Create a reclass node
@@ -497,6 +658,59 @@
return ret
+def validate_node_params(node_name, **kwargs):
+ '''
+ Validates if pillar of a node is in correct state.
+ Returns error message only if error occurred.
+
+ :param node_name: target minion ID
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt-call reclass.validate_node_params minion_id
+
+ '''
+ defaults = find_and_read_configfile()
+ meta = ''
+ error = None
+ try:
+ pillar = ext_pillar(node_name, {}, defaults['storage_type'], defaults['inventory_base_uri'])
+ except (ReclassException, Exception) as e:
+ msg = "Validation failed in %s on %s" % (repr(e), node_name)
+ LOG.error(msg)
+ meta = {'Error': msg}
+ s = str(type(e))
+ if 'yaml.scanner.ScannerError' in s:
+ error = re.sub(r"\r?\n?$", "", repr(str(e)), 1)
+ else:
+ error = e.message
+ if 'Error' in meta:
+ ret = {node_name: error}
+ else:
+ ret = {node_name: ''}
+ return ret
+
+
+def validate_nodes_params(**connection_args):
+ '''
+ Validates if pillar all known nodes is in correct state.
+ Returns error message for every node where problem occurred.
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt-call reclass.validate_nodes_params
+ '''
+ ret={}
+ nodes = node_list(**connection_args)
+ for node_name, node in nodes.items():
+ ret.update(validate_node_params(node_name))
+ return ret
+
+
def node_pillar(node_name, **kwargs):
'''
Returns pillar data for given minion from reclass inventory.