Exception handling when file-not-found
Added rudimentary exception handling for when yaml_fs encounters
a node/class for which no file exists.
Unfortunately, there is no way to properly communicate that to Ansible
when it invokes the adapter. Oh well.
Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/adapters/ansible b/adapters/ansible
index bb35e3f..2e485ec 100755
--- a/adapters/ansible
+++ b/adapters/ansible
@@ -8,12 +8,12 @@
#
import os, sys, posix, stat
-def usage_error(msg):
+def exit_error(msg, rc):
print >>sys.stderr, msg
- sys.exit(posix.EX_USAGE)
+ sys.exit(rc)
if len(sys.argv) == 1:
- usage_error('Need to specify --list or --host.')
+ exit_error('Need to specify --list or --host.', posix.EX_USAGE)
ansible_dir = os.path.dirname(sys.argv[0])
@@ -22,7 +22,7 @@
# symlinks are resolved.
realpath = os.path.realpath(sys.argv[0] + '/../../')
sys.path.insert(0, realpath)
-import reclass, config
+import reclass, config, errors
# The adapter resides in the Ansible directory, so let's look there for an
# optional configuration file called reclass-config.yml.
@@ -40,7 +40,7 @@
if stat.S_ISDIR(os.stat(nodes_uri).st_mode):
options['nodes_uri'] = nodes_uri
else:
- usage_error('nodes_uri not specified')
+ exit_error('nodes_uri not specified', posix.EX_USAGE)
if 'classes_uri' not in options:
classes_uri = os.path.join(ansible_dir, 'classes')
@@ -53,29 +53,35 @@
# False instead, we print the inventory. Yeah for option abuse!
if sys.argv[1] == '--list':
if len(sys.argv) > 2:
- usage_error('Unknown arguments: ' + ' '.join(sys.argv[2:]))
+ exit_error('Unknown arguments: ' + ' '.join(sys.argv[2:]),
+ posix.EX_USAGE)
options['node'] = False
elif sys.argv[1] == '--host':
if len(sys.argv) < 3:
- usage_error('Missing hostname.')
+ exit_error('Missing hostname.', posix.EX_USAGE)
elif len(sys.argv) > 3:
- usage_error('Unknown arguments: ' + ' '.join(sys.argv[3:]))
+ exit_error('Unknown arguments: ' + ' '.join(sys.argv[3:]),
+ posix.EX_USAGE)
options['node'] = sys.argv[2]
else:
- usage_error('Unknown mode (--list or --host required).')
+ exit_error('Unknown mode (--list or --host required).', posix.EX_USAGE)
-data = reclass.get_data(options['storage_type'], options['nodes_uri'],
- options['classes_uri'], options['node'])
+try:
+ data = reclass.get_data(options['storage_type'], options['nodes_uri'],
+ options['classes_uri'], options['node'])
-if options['node']:
- # Massage and shift the data like Ansible wants it
- data['parameters']['RECLASS'] = data['RECLASS']
- for i in ('classes', 'applications'):
- data['parameters']['RECLASS'][i] = data[i]
- data = data['parameters']
+ if options['node']:
+ # Massage and shift the data like Ansible wants it
+ data['parameters']['RECLASS'] = data['RECLASS']
+ for i in ('classes', 'applications'):
+ data['parameters']['RECLASS'][i] = data[i]
+ data = data['parameters']
-print reclass.output(data, options['output'], options['pretty_print'])
+ print reclass.output(data, options['output'], options['pretty_print'])
-sys.exit(posix.EX_OK)
+ sys.exit(posix.EX_OK)
+
+except errors.ReclassException, e:
+ exit_error(e.message, e.rc)
diff --git a/errors.py b/errors.py
new file mode 100644
index 0000000..dfb07b3
--- /dev/null
+++ b/errors.py
@@ -0,0 +1,53 @@
+#
+# -*- coding: utf-8 -*-
+#
+# This file is part of reclass (http://github.com/madduck/reclass)
+#
+# Copyright © 2007–13 martin f. krafft <madduck@madduck.net>
+# Released under the terms of the Artistic Licence 2.0
+#
+
+import posix
+
+class ReclassException(Exception):
+
+ def __init__(self, rc=posix.EX_SOFTWARE, *args):
+ super(ReclassException, self).__init__(*args)
+ self._rc = rc
+
+ def __str__(self):
+ return "reclass encountered an exception, sorry!"
+
+ message = property(lambda self: self.__str__())
+ rc = property(lambda self: self._rc)
+
+
+class NotFoundError(ReclassException):
+
+ def __init__(self, rc=posix.EX_IOERR):
+ super(NotFoundError, self).__init__(rc)
+
+
+class NodeNotFound(NotFoundError):
+
+ def __init__(self, storage, classname, uri):
+ super(NodeNotFound, self).__init__()
+ self._storage = storage
+ self._name = classname
+ self._uri = uri
+
+ def __str__(self):
+ return "Node '{0}' not found under {1}://{2}".format(self._name,
+ self._storage,
+ self._uri)
+
+
+class ClassNotFound(NodeNotFound):
+
+ def __init__(self, storage, classname, uri, nodename):
+ super(ClassNotFound, self).__init__(storage, classname, uri)
+ self._nodename = nodename
+
+ def __str__(self):
+ return "Class '{0}' (in ancestry of node {1}) not found under {2}://{3}" \
+ .format(self._name, self._nodename, self._storage, self._uri)
diff --git a/reclass.py b/reclass.py
index f0c24ce..cd44652 100755
--- a/reclass.py
+++ b/reclass.py
@@ -17,6 +17,7 @@
import config
from output import OutputLoader
from storage import StorageBackendLoader
+import errors
def get_options(config_file=None):
return config.get_options(__name__, __version__, __description__, config_file)
@@ -38,6 +39,10 @@
outputter = output_class()
return outputter.dump(data, pretty_print=pretty_print)
+def _error(msg, rc):
+ print >>sys.stderr, msg
+ sys.exit(rc)
+
if __name__ == '__main__':
__name__ = __prog__
config_file = None
@@ -46,8 +51,12 @@
if os.access(f, os.R_OK):
config_file = f
break
- options = get_options(config_file)
- data = get_data(options.storage_type, options.nodes_uri,
- options.classes_uri, options.node)
- print output(data, options.output, options.pretty_print)
- sys.exit(posix.EX_OK)
+ try:
+ options = get_options(config_file)
+ data = get_data(options.storage_type, options.nodes_uri,
+ options.classes_uri, options.node)
+ print output(data, options.output, options.pretty_print)
+ sys.exit(posix.EX_OK)
+
+ except errors.ReclassException, e:
+ _error(e.message, e.rc)
diff --git a/storage/yaml_fs/__init__.py b/storage/yaml_fs/__init__.py
index 0eff9ed..f53048f 100644
--- a/storage/yaml_fs/__init__.py
+++ b/storage/yaml_fs/__init__.py
@@ -10,6 +10,7 @@
from storage import NodeStorageBase
from yamlfile import YamlFile
from directory import Directory
+import errors
FILE_EXTENSION = '.yml'
@@ -18,16 +19,23 @@
def __init__(self, nodes_uri, classes_uri):
super(ExternalNodeStorage, self).__init__(nodes_uri, classes_uri)
- def _read_nodeinfo(self, name, base_uri, seen):
+ def _read_nodeinfo(self, name, base_uri, seen, nodename=None):
path = os.path.join(base_uri, name + FILE_EXTENSION)
- entity = YamlFile(path).entity
- seen[name] = True
- for klass in entity.classes:
- if klass not in seen:
- ret = self._read_nodeinfo(klass, self.classes_uri, seen)[0]
- ret.merge(entity)
- entity = ret
- return entity, path
+ try:
+ entity = YamlFile(path).entity
+ seen[name] = True
+ for klass in entity.classes:
+ if klass not in seen:
+ ret = self._read_nodeinfo(klass, self.classes_uri, seen,
+ name if nodename is None else nodename)[0]
+ ret.merge(entity)
+ entity = ret
+ return entity, path
+ except IOError:
+ if base_uri == self.classes_uri:
+ raise errors.ClassNotFound('yaml_fs', name, base_uri, nodename)
+ else:
+ raise errors.NodeNotFound('yaml_fs', name, base_uri)
def _list_inventory(self):
d = Directory(self.nodes_uri)