From c77eb5be3bb7692109b8aab701968355d17a36ee Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 13 Feb 2014 12:58:03 -0500 Subject: [PATCH] Have configmanager protect against unrecognized data. When doing a set, check the keynames and value types as appropriate. raise ValueError in the configmanager case since it could be code or human mistake. attributes plugin then catches this error and propogates it up as an InvalidArgumentException if it is just trying to pass in data from user. --- confluent/config/attributes.py | 6 ++--- confluent/config/configmanager.py | 34 ++++++++++++++++------------- confluent/messages.py | 27 +++++++++++------------ plugins/configuration/attributes.py | 26 +++++++++++++++------- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/confluent/config/attributes.py b/confluent/config/attributes.py index 72abc999..37c27c43 100644 --- a/confluent/config/attributes.py +++ b/confluent/config/attributes.py @@ -54,7 +54,7 @@ node = { 'groups': { 'type': (list, tuple), 'default': 'all', - 'description': ('List of static groups for which this node is' + 'description': ('List of static groups for which this node is ' 'considered a member'), }, #'type': { @@ -128,8 +128,8 @@ node = { # 'serial console configuration') # }, 'console.method': { - 'description': ('Indicate the method used to access the console of' - 'The managed node.') + 'description': ('Indicate the method used to access the console of ' + 'the managed node.') }, # 'virtualization.host': { # 'description': ('Hypervisor where this node does/should reside'), diff --git a/confluent/config/configmanager.py b/confluent/config/configmanager.py index c09c7f6c..f7fe8756 100644 --- a/confluent/config/configmanager.py +++ b/confluent/config/configmanager.py @@ -54,6 +54,7 @@ from Crypto.Hash import SHA256 import array import ast import collections +import confluent.config.attributes as attributes import confluent.util import copy import cPickle @@ -236,9 +237,9 @@ class _ExpressionFormat(string.Formatter): ast.BitOr: operator.or_, } - def __init__(self, nodeobj): + def __init__(self, nodeobj, nodename): self._nodeobj = nodeobj - self._numbers = re.findall(self.nummatch, nodeobj['name']['value']) + self._nodename = nodename def get_field(self, field_name, args, kwargs): parsed = ast.parse(field_name) @@ -246,10 +247,6 @@ class _ExpressionFormat(string.Formatter): def _handle_ast_node(self, node): if isinstance(node, ast.Num): - if '_expressionkeys' not in self._nodeobj: - self._nodeobj['_expressionkeys'] = set(['name']) - else: - self._nodeobj['_expressionkeys'].add('name') return node.n elif isinstance(node, ast.Attribute): #ok, we have something with a dot @@ -266,10 +263,12 @@ class _ExpressionFormat(string.Formatter): elif isinstance(node, ast.Name): var = node.id if var == 'nodename': - return self._nodeobj['name']['value'] + return self._nodename mg = re.match(self.posmatch, var) if mg: idx = int(mg.group(1)) + if self._numbers == None: + self._numbers = re.findall(self.nummatch, self._nodename) return int(self._numbers[idx - 1]) else: if var in self._nodeobj: @@ -471,6 +470,7 @@ class ConfigManager(object): # if the attribute is not set, this will search for a candidate # if it is set, but inheritedfrom, search for a replacement, just # in case + print repr(nodecfg['groups']) for group in nodecfg['groups']: if attrib in self._cfgstore['groups'][group]: if srcgroup is not None and group != srcgroup: @@ -498,8 +498,7 @@ class ConfigManager(object): elif group not in self._cfgstore['grouplist']: self._cfgstore['grouplist'].append(group) if group not in self._cfgstore['groups']: - self._cfgstore['groups'][group] = {'name': {'value': group}, - 'nodes': set([node]) } + self._cfgstore['groups'][group] = {'nodes': set([node])} elif 'nodes' not in self._cfgstore['groups'][group]: self._cfgstore['groups'][group]['nodes'] = set([node]) elif node not in self._cfgstore['groups'][group]['nodes']: @@ -519,8 +518,7 @@ class ConfigManager(object): self._node_removed_from_group(node, group) for node in nodes: if node not in self._cfgstore['nodes']: - self._cfgstore['nodes'][node] = {'name': {'value': node}, - 'groups': [group] } + self._cfgstore['nodes'][node] = {'groups': [group]} elif 'groups' not in self._cfgstore['nodes'][node]: self._cfgstore['nodes'][node]['groups'] = [group] elif group not in self._cfgstore['nodes'][node]['groups']: @@ -534,7 +532,7 @@ class ConfigManager(object): self._cfgstore['groups'] = {} for group in attribmap.iterkeys(): if group not in self._cfgstore['groups']: - self._cfgstore['groups'][group] = {'name': {'value': group}, 'nodes': set([])} + self._cfgstore['groups'][group] = {'nodes': set([])} cfgobj = self._cfgstore['groups'][group] for attr in attribmap[group].iterkeys(): newdict = {} @@ -571,11 +569,14 @@ class ConfigManager(object): # flows to peers, all should have the same result for node in attribmap.iterkeys(): if node not in self._cfgstore['nodes']: - self._cfgstore['nodes'][node] = {'name': {'value': node}} + self._cfgstore['nodes'][node] = {} cfgobj = self._cfgstore['nodes'][node] - exprmgr = _ExpressionFormat(cfgobj) recalcexpressions = False for attrname in attribmap[node].iterkeys(): + if (attrname not in attributes.node or + ('type' in attributes.node[attrname] and + type(attribmap[node][attrname]) not in attributes.node[attrname])): + raise ValueError newdict = {} if (isinstance(attribmap[node][attrname], str)): newdict = {'value': attribmap[node][attrname] } @@ -592,10 +593,13 @@ class ConfigManager(object): attrname in cfgobj['_expressionkeys']): recalcexpressions = True if 'expression' in cfgobj[attrname]: # evaluate now + if exprmgr is None: + exprmgr = _ExpressionFormat(cfgobj, node) cfgobj[attrname] = _decode_attribute(attrname, cfgobj, formatter=exprmgr) if recalcexpressions: - exprmgr = _ExpressionFormat(cfgobj) + if exprmgr is None: + exprmgr = _ExpressionFormat(cfgobj, node) self._recalculate_expressions(cfgobj, formatter=exprmgr) self._bg_sync_to_file() #TODO: wait for synchronization to suceed/fail??) diff --git a/confluent/messages.py b/confluent/messages.py index 7160fb08..44cb5969 100644 --- a/confluent/messages.py +++ b/confluent/messages.py @@ -35,10 +35,6 @@ class ConfluentMessage(object): val = self.kvpairs[key] value = self.defaultvalue type = self.defaulttype - try: - desc = self.desc - except: - desc = '' if 'value' in val: value = val['value'] if value is None: @@ -52,17 +48,17 @@ class ConfluentMessage(object): for v in val: snippet += ('' - ).format(type, key, v, desc) + ).format(type, key, v, self.desc) snippet += ( '' '').format(type, key, desc) + 'value="{1}">').format(type, key, self.desc) return snippet snippet += (key + ":" + '' - ).format(type, key, value, desc) + ).format(type, key, value, self.desc) return snippet @@ -264,7 +260,7 @@ class PowerState(ConfluentChoiceMessage): class Attributes(ConfluentMessage): - def __init__(self, node=None, kv=None, desc=None): + def __init__(self, node=None, kv=None, desc=''): self.desc = desc nkv = {} for key in kv.iterkeys(): @@ -278,7 +274,8 @@ class Attributes(ConfluentMessage): class ListAttributes(ConfluentMessage): - def __init__(self, node, kv): + def __init__(self, node, kv, desc=''): + self.desc = desc self .kvpairs = { node: kv } @@ -287,15 +284,17 @@ class ListAttributes(ConfluentMessage): class CryptedAttributes(Attributes): defaulttype = 'password' - def __init__(self, node=None, kv=None, desc=None): + def __init__(self, node=None, kv=None, desc=''): # for now, just keep the dictionary keys and discard crypt value self.desc = desc nkv = {} for key in kv.iterkeys(): - if kv[key]['cryptvalue'] != '': - nkv[key] = {'isset': True} - else: - nkv[key] = {'isset': False} + nkv[key] = {'isset': False} + try: + if kv[key] is not None and kv[key]['cryptvalue'] != '': + nkv[key] = {'isset': True} + except KeyError: + pass if node is None: self.kvpairs = nkv else: diff --git a/plugins/configuration/attributes.py b/plugins/configuration/attributes.py index 59503a5b..1cf81b9a 100644 --- a/plugins/configuration/attributes.py +++ b/plugins/configuration/attributes.py @@ -1,3 +1,4 @@ +import confluent.exceptions as exc import confluent.messages as msg import confluent.config.attributes as allattributes @@ -11,29 +12,35 @@ def retrieve(nodes, element, configmanager, inputdata): elif attribute == 'groups': # no setting, provide a blank val = [] else: # no setting, provide a blank - val = {'value': '', 'cryptvalue': ''} + val = {'value': None} if attribute.startswith('secret.'): yield msg.CryptedAttributes(node, - {attribute: val}) + {attribute: val}, + allattributes.node[attribute]['description']) elif isinstance(val, list): yield msg.ListAttributes(node, - {attribute: val}) + {attribute: val}, + allattributes.node[attribute]['description']) else: yield msg.Attributes(node, - {attribute: val['value']}) + {attribute: val['value']}, + allattributes.node[attribute]['description']) elif element[-1] == 'current': for node in attributes.iterkeys(): for attribute in sorted(attributes[node].iterkeys()): currattr = attributes[node][attribute] if 'value' in currattr: yield msg.Attributes(node, - {attribute: currattr['value']}) + {attribute: currattr['value']}, + allattributes.node[attribute]['description']) elif 'cryptvalue' in currattr: yield msg.CryptedAttributes(node, - {attribute: currattr}) + {attribute: currattr}, + allattributes.node[attribute]['description']) elif isinstance(currattr, list): yield msg.ListAttributes(node, - {attribute: currattr}) + {attribute: currattr}, + allattributes.node[attribute]['description']) else: print repr(currattr) raise Exception("BUGGY ATTRIBUTE FOR NODE") @@ -45,5 +52,8 @@ def update(nodes, element, configmanager, inputdata): updatenode = inputdata.get_attributes(node) if updatenode: updatedict[node] = updatenode - configmanager.set_node_attributes(updatedict) + try: + configmanager.set_node_attributes(updatedict) + except ValueError: + raise exc.InvalidArgumentException() return retrieve(nodes, element, configmanager, inputdata)