Basic framework for class mappings
Each node's classes are now pre-initialised depending on the
classes_mapping key in the config file, e.g.:
class_mappings:
'*': default
/^local/:
- local
This will cause all nodes to get the 'default' class before anything
else, and it will cause all nodes whose name start with "local" to get
assigned the 'local' class.
Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/reclass/__init__.py b/reclass/__init__.py
index 176b638..eb63cd2 100644
--- a/reclass/__init__.py
+++ b/reclass/__init__.py
@@ -11,23 +11,27 @@
from storage import StorageBackendLoader
from config import path_mangler
-def get_storage(storage_type, nodes_uri, classes_uri):
+def get_storage(storage_type, nodes_uri, classes_uri, class_mappings):
storage_class = StorageBackendLoader(storage_type).load()
- return storage_class(nodes_uri, classes_uri)
+ return storage_class(nodes_uri, classes_uri, class_mappings)
-def get_nodeinfo(storage_type, inventory_base_uri, nodes_uri, classes_uri, nodename):
+def get_nodeinfo(storage_type, inventory_base_uri, nodes_uri, classes_uri,
+ nodename, class_mappings):
nodes_uri, classes_uri = path_mangler(inventory_base_uri, nodes_uri,
classes_uri)
- storage = get_storage(storage_type, nodes_uri, classes_uri)
+ storage = get_storage(storage_type, nodes_uri, classes_uri,
+ class_mappings)
# TODO: template interpolation
return storage.nodeinfo(nodename)
-def get_inventory(storage_type, inventory_base_uri, nodes_uri, classes_uri):
+def get_inventory(storage_type, inventory_base_uri, nodes_uri, classes_uri,
+ class_mappings):
nodes_uri, classes_uri = path_mangler(inventory_base_uri, nodes_uri,
classes_uri)
- storage = get_storage(storage_type, nodes_uri, classes_uri)
+ storage = get_storage(storage_type, nodes_uri, classes_uri,
+ class_mappings)
return storage.inventory()
diff --git a/reclass/adapters/ansible.py b/reclass/adapters/ansible.py
index 404481a..cdde59b 100755
--- a/reclass/adapters/ansible.py
+++ b/reclass/adapters/ansible.py
@@ -52,11 +52,13 @@
nodeinfo_help='output host_vars for the given host',
add_options_cb=add_ansible_options_group,
defaults=defaults)
+ class_mappings = defaults.get('class_mappings')
if options.mode == MODE_NODEINFO:
data = get_nodeinfo(options.storage_type,
options.inventory_base_uri, options.nodes_uri,
- options.classes_uri, options.hostname)
+ options.classes_uri, options.hostname,
+ class_mappings)
# Massage and shift the data like Ansible wants it
data['parameters']['__reclass__'] = data['__reclass__']
for i in ('classes', 'applications'):
@@ -66,7 +68,8 @@
else:
data = get_inventory(options.storage_type,
options.inventory_base_uri,
- options.nodes_uri, options.classes_uri)
+ options.nodes_uri, options.classes_uri,
+ class_mappings)
# Ansible inventory is only the list of groups. Groups are the set
# of classes plus the set of applications with the postfix added:
groups = data['classes']
diff --git a/reclass/adapters/salt.py b/reclass/adapters/salt.py
index 8a28c84..0edbd9c 100755
--- a/reclass/adapters/salt.py
+++ b/reclass/adapters/salt.py
@@ -74,19 +74,22 @@
nodeinfo_dest='nodename',
nodeinfo_help='output pillar data for a specific node',
defaults=defaults)
+ class_mappings = defaults.get('class_mappings')
if options.mode == MODE_NODEINFO:
data = ext_pillar(options.nodename, {},
storage_type=options.storage_type,
inventory_base_uri=options.inventory_base_uri,
nodes_uri=options.nodes_uri,
- classes_uri=options.classes_uri)
+ classes_uri=options.classes_uri,
+ class_mappings=class_mappings)
else:
data = top(minion_id=None,
storage_type=options.storage_type,
inventory_base_uri=options.inventory_base_uri,
nodes_uri=options.nodes_uri,
- classes_uri=options.classes_uri)
+ classes_uri=options.classes_uri,
+ class_mappings=class_mappings)
print output(data, options.output, options.pretty_print)
diff --git a/reclass/cli.py b/reclass/cli.py
index 85ea949..e5b9f0c 100644
--- a/reclass/cli.py
+++ b/reclass/cli.py
@@ -24,14 +24,17 @@
defaults.update(find_and_read_configfile())
options = get_options(RECLASS_NAME, VERSION, DESCRIPTION,
defaults=defaults)
+ class_mappings = defaults.get('class_mappings')
if options.mode == MODE_NODEINFO:
data = get_nodeinfo(options.storage_type,
options.inventory_base_uri, options.nodes_uri,
- options.classes_uri, options.nodename)
+ options.classes_uri, options.nodename,
+ class_mappings)
else:
data = get_inventory(options.storage_type,
options.inventory_base_uri,
- options.nodes_uri, options.classes_uri)
+ options.nodes_uri, options.classes_uri,
+ class_mappings)
print output(data, options.output, options.pretty_print)
diff --git a/reclass/storage/__init__.py b/reclass/storage/__init__.py
index c2161e1..d4d4d35 100644
--- a/reclass/storage/__init__.py
+++ b/reclass/storage/__init__.py
@@ -7,8 +7,12 @@
# Released under the terms of the Artistic Licence 2.0
#
-import time, sys
-from reclass.datatypes import Entity
+import time
+import types
+import re
+import sys
+import fnmatch
+from reclass.datatypes import Entity, Classes
def _get_timestamp():
return time.strftime('%c')
@@ -19,13 +23,39 @@
class NodeStorageBase(object):
- def __init__(self, nodes_uri, classes_uri):
+ def __init__(self, nodes_uri, classes_uri, class_mappings):
self._nodes_uri = nodes_uri
self._classes_uri = classes_uri
self._classes_cache = {}
+ self._class_mappings = class_mappings
nodes_uri = property(lambda self: self._nodes_uri)
classes_uri = property(lambda self: self._classes_uri)
+ class_mappings = property(lambda self: self._class_mappings)
+
+ def _match_regexp(self, key, nodename):
+ return re.search(key, nodename)
+
+ def _match_glob(self, key, nodename):
+ return fnmatch.fnmatchcase(nodename, key)
+
+ def _populate_with_class_mappings(self, nodename):
+ c = Classes()
+ for key, value in self.class_mappings.iteritems():
+ match = False
+ if key.startswith('/') and key.endswith('/'):
+ match = self._match_regexp(key[1:-1], nodename)
+ else:
+ match = self._match_glob(key, nodename)
+ if match:
+ if isinstance(value, (types.ListType, types.TupleType)):
+ for v in value:
+ c.append_if_new(v)
+ else:
+ c.append_if_new(value)
+
+ return Entity(classes=c,
+ name='class mappings for node {0}'.format(nodename))
def _get_storage_name(self):
raise NotImplementedError, "Storage class does not have a name"
@@ -66,7 +96,7 @@
def _nodeinfo(self, nodename):
node_entity = self._get_node(nodename)
- merge_base = Entity(name='merge base for {0}'.format(nodename))
+ merge_base = self._populate_with_class_mappings(nodename)
ret = self._recurse_entity(node_entity, merge_base, nodename=nodename)
ret.interpolate()
return ret
@@ -74,7 +104,7 @@
def _nodeinfo_as_dict(self, nodename, entity):
ret = {'__reclass__' : {'node': nodename, 'uri': entity.uri,
'timestamp': _get_timestamp()
- },
+ },
}
ret.update(entity.as_dict())
return ret
diff --git a/reclass/storage/yaml_fs/__init__.py b/reclass/storage/yaml_fs/__init__.py
index 6569709..72bfc88 100644
--- a/reclass/storage/yaml_fs/__init__.py
+++ b/reclass/storage/yaml_fs/__init__.py
@@ -22,8 +22,9 @@
class ExternalNodeStorage(NodeStorageBase):
- def __init__(self, nodes_uri, classes_uri):
- super(ExternalNodeStorage, self).__init__(nodes_uri, classes_uri)
+ def __init__(self, nodes_uri, classes_uri, class_mappings):
+ super(ExternalNodeStorage, self).__init__(nodes_uri, classes_uri,
+ class_mappings)
def _handle_node_duplicates(name, uri1, uri2):
raise reclass.errors.DuplicateNodeNameError(self._get_storage_name(),