Allow classes to be namespaced with subdirectories
Classes files may now reside in subdirectories, which act as namespaces.
For instance, a class ``ssh.server`` will result in the class definition
to be read from ``ssh/server.yml``. Specifying just ``ssh`` will cause
the class data to be read from ``ssh/init.yml`` or ``ssh.yml``. Note,
however, that only one of those two may be present.
Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index a0ab3db..25bda17 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -5,6 +5,8 @@
========= ========== ========================================================
Version Date Changes
========= ========== ========================================================
+ * yaml_fs: classes may be defined in subdirectories
+ (closes: #12, #19, #20)
* Migrate Salt adapter to new core API (closes: #18)
* Fix --nodeinfo invocation in docs (closes: #21)
1.2.2 2013-12-27 * Recurse classes obtained from class mappings
diff --git a/doc/source/operations.rst b/doc/source/operations.rst
index 42e8a86..365c198 100644
--- a/doc/source/operations.rst
+++ b/doc/source/operations.rst
@@ -42,11 +42,17 @@
into which the node definition is supposed to be place.
============ ================================================================
-Nodes may be defined in subdirectories. However, node names (filename) must be
-unique across all subdirectories, and |reclass| will exit with an error if
-a node is defined multiple times. Subdirectories therefore really only exist
-for the administrator's local data structuring. They may be used in mappings
-(see below) to tag additional classes onto nodes.
+Classes files may reside in subdirectories, which act as namespaces. For
+instance, a class ``ssh.server`` will result in the class definition to be
+read from ``ssh/server.yml``. Specifying just ``ssh`` will cause the class
+data to be read from ``ssh/init.yml`` or ``ssh.yml``. Note, however, that only
+one of those two may be present.
+
+Nodes may also be defined in subdirectories. However, node names (filename)
+must be unique across all subdirectories, and |reclass| will exit with an
+error if a node is defined multiple times. Subdirectories therefore really
+only exist for the administrator's local data structuring. They may be used in
+mappings (see below) to tag additional classes onto nodes.
Data merging
------------
diff --git a/doc/source/todo.rst b/doc/source/todo.rst
index 38df768..18b39ed 100644
--- a/doc/source/todo.rst
+++ b/doc/source/todo.rst
@@ -57,12 +57,4 @@
Furthermore, ``$CWD`` and ``~`` might not make a lot of sense in all
use-cases.
-Class subdirectories
---------------------
-It would be nice syntactic sugar to allow classes to sit in subdirectories,
-such that ``ssh.server`` would load a class in …/ssh/server.yml (assuming
-``yaml_fs``).
-
-See `this pull request for a discussion about it <https://github.com/madduck/reclass/pull/12>`_.
-
.. include:: substs.inc
diff --git a/examples/classes/basenode.yml b/examples/classes/node/index.yml
similarity index 100%
rename from examples/classes/basenode.yml
rename to examples/classes/node/index.yml
diff --git a/examples/classes/unixnode.yml b/examples/classes/node/unix.yml
similarity index 83%
rename from examples/classes/unixnode.yml
rename to examples/classes/node/unix.yml
index 26c6583..12bbac1 100644
--- a/examples/classes/unixnode.yml
+++ b/examples/classes/node/unix.yml
@@ -1,5 +1,5 @@
classes:
- - basenode
+ - node
applications:
- motd
parameters:
diff --git a/examples/nodes/localhost.yml b/examples/nodes/localhost.yml
index 2b2612e..45f712e 100644
--- a/examples/nodes/localhost.yml
+++ b/examples/nodes/localhost.yml
@@ -1,5 +1,5 @@
classes:
- - unixnode
+ - node.unix
- mysite
environment: testing
parameters:
diff --git a/reclass/storage/yaml_fs/__init__.py b/reclass/storage/yaml_fs/__init__.py
index 6c917e0..e88fd7d 100644
--- a/reclass/storage/yaml_fs/__init__.py
+++ b/reclass/storage/yaml_fs/__init__.py
@@ -26,31 +26,43 @@
def __init__(self, nodes_uri, classes_uri, default_environment=None):
super(ExternalNodeStorage, self).__init__(STORAGE_NAME)
- def _handle_node_duplicates(name, uri1, uri2):
- raise reclass.errors.DuplicateNodeNameError(self._get_storage_name(),
- name, uri1, uri2)
+ def name_mangler(relpath, name):
+ # nodes are identified just by their basename
+ return name
self._nodes_uri = nodes_uri
- self._nodes = self._enumerate_inventory(nodes_uri,
- duplicate_handler=_handle_node_duplicates)
+ self._nodes = self._enumerate_inventory(nodes_uri, name_mangler)
+
+ def name_mangler(relpath, name):
+ if relpath == '.':
+ return name
+ parts = relpath.split(os.path.sep)
+ if name != 'index':
+ parts.append(name)
+ return '.'.join(parts)
self._classes_uri = classes_uri
- self._classes = self._enumerate_inventory(classes_uri)
+ self._classes = self._enumerate_inventory(classes_uri, name_mangler)
self._default_environment = default_environment
nodes_uri = property(lambda self: self._nodes_uri)
classes_uri = property(lambda self: self._classes_uri)
- def _enumerate_inventory(self, basedir, duplicate_handler=None):
+ def _enumerate_inventory(self, basedir, name_mangler):
ret = {}
def register_fn(dirpath, filenames):
filenames = fnmatch.filter(filenames, '*{0}'.format(FILE_EXTENSION))
vvv('REGISTER {0} in path {1}'.format(filenames, dirpath))
for f in filenames:
name = os.path.splitext(f)[0]
+ relpath = os.path.relpath(dirpath, basedir)
+ if callable(name_mangler):
+ name = name_mangler(relpath, name)
uri = os.path.join(dirpath, f)
- if name in ret and callable(duplicate_handler):
- duplicate_handler(name, os.path.join(basedir, ret[name]), uri)
- ret[name] = os.path.relpath(uri, basedir)
+ if name in ret:
+ E = reclass.errors.DuplicateNodeNameError
+ raise E(self._get_storage_name(), name,
+ os.path.join(basedir, ret[name]), uri)
+ ret[name] = os.path.join(relpath, f)
d = Directory(basedir)
d.walk(register_fn)
@@ -70,6 +82,7 @@
def get_class(self, name, nodename=None):
vvv('GET CLASS {0}'.format(name))
try:
+ print self._classes
path = os.path.join(self.classes_uri, self._classes[name])
except KeyError, e:
raise reclass.errors.ClassNotFound(self.name, name, self.classes_uri)