Massive refactoring
This commit does some massive refactoring of the Salt source:
- reclass and all adapters have been changed to support the distribute
(setuptools) entry-points interface, while they are also runnable
directly (using `/usr/bin/env python`);
- reclass and all adapters now use exactly the same approach to
configuration (sensible defaults + config file + command-line
options), but inventory/nodeinfo is properly parametrised (e.g.
top/pillar for Salt);
- the documentation has been updated accordingly;
- defaults and constants were factored out into common modules.
Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/README b/README
index 35e7478..3bea51b 100644
--- a/README
+++ b/README
@@ -16,12 +16,12 @@
ext_pillar/master_tops for Salt, or /etc/ansible/hosts).
reclass allows you to define your nodes through class inheritance, while
-always able to override details of classes further up the tree. Think of
-classes as feature sets, as commonalities between nodes, or as tags. Add to
-that the ability to nest classes (multiple inheritance is allowed,
-well-defined, and encouraged), and piece together your infrastructure from
-smaller bits, eliminating redundancy and exposing all important parameters to
-a single location, logically organised.
+always able to override details further up the tree (i.e. in more specific
+nodes). Think of classes as feature sets, as commonalities between nodes, or
+as tags. Add to that the ability to nest classes (multiple inheritance is
+allowed, well-defined, and encouraged), and piece together your infrastructure
+from smaller bits, eliminating redundancy and exposing all important
+parameters to a single location, logically organised.
In general, the ENC fulfills two jobs:
@@ -36,40 +36,41 @@
~~~~~~~~~~~~
Before you can use reclass, you need to install it into a place where Python
can find it. Unless you installed a package from your distribution, the
-following step:
+following step should install the package to /usr/local:
- python setup.py install
-
-This will install the package to /usr/local, which is likely in your Python
-path. You can check this using
-
- python -c 'import sys; print sys.path'
+ $ python setup.py install
If you want to install to a different location, use --prefix like so:
- python setup.py install --prefix=/opt/local
+ $ python setup.py install --prefix=/opt/local
+
+Just make sure that the destination is in the Python module search path, which
+you can check like this:
+
+ $ python -c 'import sys; print sys.path'
More options can be found in the output of
- python setup.py install --help
- python setup.py --help
- python setup.py --help-commands
- python setup.py --help [cmd]
+ $ python setup.py install --help
+ $ python setup.py --help
+ $ python setup.py --help-commands
+ $ python setup.py --help [cmd]
If you just want to run reclass from source, e.g. because you are going to be
making and testing changes, install it in "development mode":
- python setup.py develop
+ $ python setup.py develop
-To uninstall:
+To uninstall (the rm call is necessary due to http://bugs.debian.org/714960):
- python setup.py develop --uninstall
+ $ python setup.py develop --uninstall
+ $ rm /usr/local/bin/reclass*
Uninstallation currently isn't possible for packages installed to /usr/local
as per the above method, unfortunately: http://bugs.python.org/issue4673.
The following should do:
- rm -r /usr/local/lib/python*/dist-packages/reclass* /usr/local/bin/reclass
+ $ rm -r /usr/local/lib/python*/dist-packages/reclass* /usr/local/bin/reclass*
reclass concepts
~~~~~~~~~~~~~~~~
@@ -86,7 +87,7 @@
node: A node, usually a computer in your infrastructure
class: A category, tag, feature, or role that applies to a node
Classes may be nested, i.e. there can be a class hierarchy
- application: A specific set of behaviour to apply to members of a class
+ application: A specific set of behaviour to apply
parameter: Node-specific variables, with inheritance throughout the class
hierarchy.
@@ -229,8 +230,12 @@
reclass starts out reading a node definition file, obtains the list of
classes, then reads the files corresponding to these classes, recursively
-reading parent classes, and finally merges the applications list (append
-unless
+reading parent classes, and finally merges the applications list and the
+parameters.
+
+Merging of parameters is done recursively, meaning that lists and dictionaries
+are extended, rather than replaced. There is currently no way to remove or
+overwrite an existing value.
Version control
~~~~~~~~~~~~~~~
@@ -239,8 +244,8 @@
Usage
~~~~~
-For information on how to use reclass directly, invoke reclass.py with --help
-and study the output.
+For information on how to use reclass directly, invoke reclass with --help and
+study the output.
The three options --inventory-base-uri, --nodes-uri, and --classes-uri
together specify the location of the inventory. If the base URI is specified,
@@ -248,13 +253,13 @@
these two URIs are not specified, they default to 'nodes' and 'classes'.
Therefore, if your inventory is in '/etc/reclass/nodes' and
'/etc/reclass/classes', all you need to specify is the base URI as
-'/etc/reclass'.
+'/etc/reclass' — which is actually the default (see reclass/defaults.py).
If you've installed reclass as per the above instructions, try to run it from
the source directory like this:
- reclass -b examples/ --inventory
- reclass -b examples/ --node localhost
+ $ reclass -b examples/ --inventory
+ $ reclass -b examples/ --node localhost
Those data come from examples/nodes and examples/classes, and you can surely
make your own way from here.
@@ -263,7 +268,8 @@
so-called adapters, e.g. /…/reclass/adapters/salt. The job of an adapter is to
translate between different invocation paradigms, provide a sane set of
default options, and massage the data from reclass into the format expected by
-the automation tool in use.
+the automation tool in use. Please have a look at the respective README files
+for these adapters.
Configuration file
~~~~~~~~~~~~~~~~~~
@@ -282,7 +288,15 @@
nodes_uri: ../nodes
reclass first looks in the current directory for the file called
-'reclass-config.yml' and if no such file is found, it looks "next to" the
-reclass script itself. Adapters implement their own lookup logic.
+'reclass-config.yml' (see reclass/defaults.py) and if no such file is found,
+it looks in $HOME, then in /etc/reclass, and then "next to" the reclass script
+itself, i.e. if the script is symlinked to /srv/provisioning/reclass, then the
+the script will try to access /srv/provisioning/reclass-config.yml.
- -- martin f. krafft <madduck@madduck.net> Fri, 03 Jul 2013 19:57:05 +0200
+Note that yaml_fs is currently the only supported storage_type, and it's the
+default if you don't set it.
+
+Adapters may implement their own lookup logic, of course, so make sure to read
+their READMEs.
+
+ -- martin f. krafft <madduck@madduck.net> Thu, 04 Jul 2013 22:20:20 +0200
diff --git a/README.Ansible b/README.Ansible
index b029325..6fa013a 100644
--- a/README.Ansible
+++ b/README.Ansible
@@ -9,9 +9,8 @@
alongside this document.
IMPORTANT NOTICE: I was kicked out of the Ansible community, and therefore
-I have no interest in developing this adapter anymore. If you use it and want
-to turn it into a setuptools entrypoints compatible adapter, I will take your
-patch.
+I have no interest in developing this adapter anymore. If you use it and have
+changes, I will take your patch.
Quick start with Ansible
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -25,11 +24,12 @@
/…/reclass refers to the location of your reclass checkout.
- 0. Run 'make' in the root of the reclass checkout (see the section
- 'Installation' in the README file for the reason).
+ 0. You can optionally install reclass from source as per the section
+ 'Installation' in the README file, or just run from source.
- 1. Symlink /…/reclass/adapters/ansible to /etc/ansible/hosts (or
- ./hacking/hosts)
+ 1. Symlink /usr/local/bin/reclass-ansible (or wherever your distro put that
+ file), or /…/reclass/reclass/adapters/ansible.py (if running from source)
+ to /etc/ansible/hosts (or ./hacking/hosts)
2. Copy the two directories 'nodes' and 'classes' from the example
subdirectory in the reclass checkout to /etc/ansible
@@ -46,31 +46,33 @@
3. Check out your inventory by invoking
- ./hosts --list
+ $ ./hosts --list
which should return 5 groups in JSON-format, and each group has exactly
one member 'localhost'.
4. See the node information for 'localhost':
- ./hosts --host localhost
+ $ ./hosts --host localhost
This should print a set of keys and values, including a greeting,
a colour, and a sub-class called '__reclas__'.
5. Execute some ansible commands, e.g.
- ansible -i hosts \* --list-hosts
- ansible -i hosts \* -m ping
- ansible -i hosts \* -m debug -a 'msg="${greeting}"'
- ansible -i hosts \* -m setup
- ansible-playbook -i hosts test.yml
+ $ ansible -i hosts \* --list-hosts
+ $ ansible -i hosts \* -m ping
+ $ ansible -i hosts \* -m debug -a 'msg="${greeting}"'
+ $ ansible -i hosts \* -m setup
+ $ ansible-playbook -i hosts test.yml
6. You can also invoke reclass directly, which gives a slightly different
view onto the same data, i.e. before it has been adapted for Ansible:
- /…/reclass.py --pretty-print --inventory
- /…/reclass.py --pretty-print --nodeinfo localhost
+ $ /…/reclass/reclass.py --pretty-print --inventory
+ $ /…/reclass/reclass.py --pretty-print --nodeinfo localhost
+
+ Or, if reclass is properly installed, just use the reclass command.
Integration with Ansible
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -174,3 +176,5 @@
Now you just need to specify realm somewhere. The reference can reside in
a parent class, while the variable is defined e.g. in the node.
+
+ -- martin f. krafft <madduck@madduck.net> Thu, 04 Jul 2013 22:20:20 +0200
diff --git a/README.Hacking b/README.Hacking
index b70be09..028f842 100644
--- a/README.Hacking
+++ b/README.Hacking
@@ -12,6 +12,9 @@
python setup.py develop
+Now the reclass script, as well as the adapters, will be available in
+/usr/local/bin, and you can also invoke them directly from the source tree.
+
To uninstall:
python setup.py develop --uninstall
@@ -37,4 +40,4 @@
If you have larger ideas, I'll be looking forward to discuss them with you.
- -- martin f. krafft <madduck@madduck.net> Wed, 03 Jul 2013 20:02:39 +0200
+ -- martin f. krafft <madduck@madduck.net> Thu, 04 Jul 2013 22:20:20 +0200
diff --git a/README.Salt b/README.Salt
index e9777cd..1b5abf1 100644
--- a/README.Salt
+++ b/README.Salt
@@ -12,98 +12,106 @@
~~~~~~~~~~~~~~~~~~~~~
The following steps should get you up and running quickly. You will need to
decide for yourself where to put your reclass inventory. This can be
-/etc/reclass, or it could be /srv/salt, for instance if /srv/salt/states is
-where your Salt file_roots live. The following shall assume the latter.
+your first base file_root (the default), or it could be /etc/reclass, or
+/srv/salt. The following shall assume the latter.
Or you can also just look into ./examples/salt of your reclass checkout,
where the following steps have already been prepared.
/…/reclass refers to the location of your reclass checkout.
- 0. Run 'make' in the root of the reclass checkout (see the section
- 'Installation' in the README file for the reason).
+ 0. Complete the installation steps described in the section 'Installation'
+ in the main README file.
- 1. Symlink /…/reclass/adapters/salt to /srv/salt/states/reclass. This is not
- at all required, because Salt interfaces with reclass as a Python module,
- but it's handy to have the inventory within reach.
+ Alternatively, you can also tell Salt via the master config file where to
+ look for reclass, but then you won't be able to interact with reclass
+ through the command line.
- 2. Copy the two directories 'nodes' and 'classes' from the example
- subdirectory in the reclass checkout to /srv/salt/states
+ 1. Copy the two directories 'nodes' and 'classes' from the example
+ subdirectory in the reclass checkout to e.g. /srv/salt.
- If you prefer to put those directories elsewhere, you can create
- /srv/salt/states/reclass-config.yml with contents such as
+ It's handy to symlink reclass' Salt adapter itself to that directory.
+
+ $ ln -s $(which salt-reclass) /srv/salt/states/reclass
+
+ As you can now just inspect the data right there from the command line:
+
+ $ ./reclass --top
+
+ If you don't want to do this, you can also let reclass know where to look
+ for the inventory with the following contents in
+ $HOME/reclass-config.yml:
storage_type: yaml_fs
nodes_uri: /srv/reclass/nodes
classes_uri: /srv/reclass/classes
+ Or you can reuse the first entry of 'file_roots' under 'base' in the Salt
+ master config.
+
Note that yaml_fs is currently the only supported storage_type, and it's
the default if you don't set it.
- Again, this isn't really required, but it's good to get you started. If
- you really put your inventory into /srv/reclass or /etc/reclass, you'll
- tell the Salt master later.
+ 2. Check out your inventory by invoking
- 3. Check out your inventory by invoking
-
- ./reclass --top
+ $ salt-reclass --top
which should return all the information about all defined nodes, which is
only 'localhost' in the example. This is essentially the same information
that you would keep in your top.sls file.
- 4. See the pillar information for 'localhost':
+ If you symlinked the script to your inventory base directory, use
- ./reclass --pillar localhost
+ $ ./reclass --top
- This is the so-called pillar-data for the named host.
+ 3. See the pillar information for 'localhost':
- 5. Now add reclass to /etc/salt/master, like so:
+ $ salt-reclass --pillar localhost
+
+ 4. Now add reclass to /etc/salt/master, like so:
+
+ reclass: &reclass
+ inventory_base_uri: /srv/salt
+ reclass_source_path: ~/code/reclass
master_tops:
- […]
- reclass:
- inventory_base_uri: /srv/salt
+ […]
+ reclass: *reclass
ext_pillar:
- - reclass:
- inventory_base_uri: /srv/salt
+ - reclass: *reclass
- Currently, there is no way to unify these configuration data, but it's
- hardly much to duplicate. In the future, I may provide for a global
- 'reclass' key, but for now you will have to add the data twice.
-
- Now restart your Salt master and make sure that reclass is in the
- PYTHONPATH, so if it's not properly installed (but you are running it
- from source), do this:
+ If you did not install reclass, you can either specify the source path
+ like above, or you can add it to PYTHONPATH before invoking the Salt
+ master, to ensure that Python can find it:
PYTHONPATH=/…/reclass /etc/init.d/salt-master restart
- 6. Provided that you have set up 'localhost' as a Salt minion, the following
+ 5. Provided that you have set up 'localhost' as a Salt minion, the following
commands should now return the same data as above, but processed through
salt:
- salt localhost pillar.items # shows just the parameters
- salt localhost state.show_top # shows only the states (applications)
+ $ salt localhost pillar.items # shows just the parameters
+ $ salt localhost state.show_top # shows only the states (applications)
Alternatively, if you don't have the Salt minion running yet:
- salt-call pillar.items # shows just the parameters
- salt-call state.show_top # shows only the states (applications)
+ # salt-call pillar.items # shows just the parameters
+ # salt-call state.show_top # shows only the states (applications)
- 7. You can also invoke reclass directly, which gives a slightly different
+ 6. You can also invoke reclass directly, which gives a slightly different
view onto the same data, i.e. before it has been adapted for Salt:
- /…/reclass.py --pretty-print --inventory
- /…/reclass.py --pretty-print --nodeinfo localhost
+ $ reclass --inventory
+ $ reclass --nodeinfo localhost
Integration with Salt
~~~~~~~~~~~~~~~~~~~~~
reclass hooks into Salt at two different points: master_tops and ext_pillar.
For both, Salt provides plugins. These plugins need to know where to find
-reclass, so if reclass is not properly installed (but you are running it
-from source), make sure to export PYTHONPATH accordingly before you start your
-Salt master.
+reclass, so if reclass is not properly installed (but you are running it from
+source), make sure to export PYTHONPATH accordingly before you start your Salt
+master, or specify the path in the master configuration file, as show above.
Salt has no concept of "nodes", "applications", "parameters", and "classes".
Therefore it is necessary to explain how those correspond to Salt. Crudely,
@@ -150,3 +158,5 @@
Instead, the interpolation needs to happen at the data structure level inside
reclass, or maybe at the adapter level, reusing the templating of Salt. This
will require some more thought, but it's on the horizon…
+
+ -- martin f. krafft <madduck@madduck.net> Thu, 04 Jul 2013 22:20:20 +0200
diff --git a/TODO b/TODO
index 65025dd..650475e 100644
--- a/TODO
+++ b/TODO
@@ -5,8 +5,5 @@
- Tests for outputters
- Improve testing of yaml_fs, maybe with more realistic examples
- Configurable file extension (.yaml/.yml, or support both)
-- Improve the adapters, either by just letting them build their own
- OptionParsers, or through setuptools entrypoints (probably best…). The
- reclass API would need a bit of generalisation for that to happen…
- Variable interpolation
- Verbosity options, debugging
diff --git a/adapters/.gitignore b/adapters/.gitignore
deleted file mode 100644
index cfa52e9..0000000
--- a/adapters/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/ansible
-/salt
diff --git a/adapters/ansible.in b/adapters/ansible.in
deleted file mode 100644
index b6e4f8f..0000000
--- a/adapters/ansible.in
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# ansible-adapter — adapter between Ansible and reclass
-#
-# IMPORTANT NOTICE: I was kicked out of the Ansible community, and therefore
-# I have no interest in developing this adapter anymore. If you use it and
-# want to turn it into a setuptools entrypoints compatible adapter, I will
-# take your patch.
-#
-# Copyright © 2007–13 martin f. krafft <madduck@madduck.net>
-# Released under the terms of the Artistic Licence 2.0
-#
-import os, sys, posix
-
-ansible_dir = os.path.dirname(sys.argv[0])
-
-# In order to be able to use reclass as modules, manipulate the search
-# path, starting from the location of the adapter. Realpath will make
-# sure that symlinks are resolved.
-realpath = os.path.realpath(sys.argv[0] + '/../../')
-sys.path.insert(0, realpath)
-from reclass.adapters.ansible import ansible_adapter
-
-def exc_handler(message, rc):
- print >>sys.stderr, message
- sys.exit(rc)
-
-ansible_adapter(ansible_dir, exc_handler)
-sys.exit(posix.EX_OK)
diff --git a/adapters/salt.in b/adapters/salt.in
deleted file mode 100644
index 380ffe6..0000000
--- a/adapters/salt.in
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# salt-adapter — adapter between Salt and reclass
-#
-# Note that this file is not really necessary and exists mostly for debugging
-# purposes and admin joys. Have a look at README.Salt for proper integration
-# between Salt and reclass.
-#
-# Copyright © 2007–13 martin f. krafft <madduck@madduck.net>
-# Released under the terms of the Artistic Licence 2.0
-#
-__name__ = 'salt-reclass'
-__description__ = 'reclass adapter for Salt'
-__version__ = '1.0'
-__author__ = 'martin f. krafft <madduck@madduck.net>'
-__copyright__ = 'Copyright © 2007–13 ' + __author__
-__licence__ = 'Artistic Licence 2.0'
-
-import sys, os, posix
-
-# In order to be able to use reclass as modules, manipulate the search
-# path, starting from the location of the adapter. Realpath will make
-# sure that symlinks are resolved.
-realpath = os.path.realpath(sys.argv[0] + '/../../')
-sys.path.insert(0, realpath)
-
-import os, sys, posix
-import reclass.config
-from reclass.output import OutputLoader
-from reclass.storage import StorageBackendLoader
-from reclass.errors import ReclassException, InvocationError
-from reclass import output
-from reclass.adapters.salt import ext_pillar, top
-
-def _error(msg, rc):
- print >>sys.stderr, msg
- sys.exit(rc)
-
-try:
- if len(sys.argv) == 1:
- raise InvocationError('Need to specify --top or --pillar.',
- posix.EX_USAGE)
-
- # initialise options and try to read ./reclass-config.yml, which is
- # expected to sit next to the salt-reclass CLI
- options = {'storage_type': 'yaml_fs',
- 'pretty_print' : True,
- 'output' : 'yaml'
- }
- basedir = os.path.dirname(sys.argv[0])
- config_path = os.path.join(basedir, 'reclass-config.yml')
- if os.path.exists(config_path) and os.access(config_path, os.R_OK):
- options.update(reclass.config.read_config_file(config_path))
-
- nodes_uri, classes_uri = reclass.config.path_mangler(options.get('inventory_base_uri'),
- options.get('nodes_uri'),
- options.get('classes_uri'))
- options['nodes_uri'] = nodes_uri
- options['classes_uri'] = classes_uri
-
- if sys.argv[1] in ('--top', '-t'):
- if len(sys.argv) > 2:
- raise InvocationError('Unknown arguments: ' + \
- ' '.join(sys.argv[2:]), posix.EX_USAGE)
- node = False
-
- elif sys.argv[1] in ('--pillar', '-p'):
- if len(sys.argv) < 3:
- raise InvocationError('Missing hostname.', posix.EX_USAGE)
- elif len(sys.argv) > 3:
- raise InvocationError('Unknown arguments: ' + \
- ' '.join(sys.argv[3:]), posix.EX_USAGE)
- node = sys.argv[2]
-
- else:
- raise InvocationError('Unknown mode (--top or --pillar required).',
- posix.EX_USAGE)
-
- if not node:
- reclass_opts = options.copy()
- del reclass_opts['output']
- del reclass_opts['pretty_print']
- data = top(**reclass_opts)
-
- else:
- pillar={}
- data = ext_pillar(node, pillar, options.get('storage_type'),
- options.get('inventory_base_uri'),
- options.get('nodes_uri'),
- options.get('classes_uri'))
-
- print output(data, options.get('output'), options.get('pretty_print'))
- sys.exit(posix.EX_OK)
-
-except ReclassException, e:
- _error(e.message, e.rc)
diff --git a/examples/ansible/hosts b/examples/ansible/hosts
deleted file mode 120000
index ea0560e..0000000
--- a/examples/ansible/hosts
+++ /dev/null
@@ -1 +0,0 @@
-../../adapters/ansible
\ No newline at end of file
diff --git a/examples/ansible/hosts b/examples/ansible/hosts
new file mode 100755
index 0000000..de52e8e
--- /dev/null
+++ b/examples/ansible/hosts
@@ -0,0 +1,3 @@
+#!/bin/sh
+cd ../../
+PYTHONPATH="`pwd`:$PYTHONPATH" exec python reclass/adapters/ansible.py "$@"
diff --git a/examples/ansible/reclass-config.yml b/examples/ansible/reclass-config.yml
index 88ac5ad..01b8d31 100644
--- a/examples/ansible/reclass-config.yml
+++ b/examples/ansible/reclass-config.yml
@@ -1,2 +1 @@
-nodes_uri: ../nodes
-classes_uri: ../classes
+inventory_base_uri: ..
diff --git a/examples/salt/reclass b/examples/salt/reclass
deleted file mode 120000
index 0d84784..0000000
--- a/examples/salt/reclass
+++ /dev/null
@@ -1 +0,0 @@
-../../adapters/salt
\ No newline at end of file
diff --git a/examples/salt/reclass b/examples/salt/reclass
new file mode 100755
index 0000000..7e2520d
--- /dev/null
+++ b/examples/salt/reclass
@@ -0,0 +1,3 @@
+#!/bin/sh
+cd ../../
+PYTHONPATH="`pwd`:$PYTHONPATH" exec python reclass/adapters/salt.py "$@"
diff --git a/examples/salt/reclass-config.yml b/examples/salt/reclass-config.yml
new file mode 100644
index 0000000..01b8d31
--- /dev/null
+++ b/examples/salt/reclass-config.yml
@@ -0,0 +1 @@
+inventory_base_uri: ..
diff --git a/reclass.py b/reclass.py
new file mode 100755
index 0000000..a0d8eb8
--- /dev/null
+++ b/reclass.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+# -*- 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 reclass.cli
+reclass.cli.main()
diff --git a/reclass/__init__.py b/reclass/__init__.py
index cf4184e..176b638 100644
--- a/reclass/__init__.py
+++ b/reclass/__init__.py
@@ -9,16 +9,27 @@
from output import OutputLoader
from storage import StorageBackendLoader
+from config import path_mangler
-def get_data(storage_type, nodes_uri, classes_uri, node):
+def get_storage(storage_type, nodes_uri, classes_uri):
storage_class = StorageBackendLoader(storage_type).load()
- storage = storage_class(nodes_uri, classes_uri)
- if not node:
- ret = storage.inventory()
- else:
- ret = storage.nodeinfo(node)
+ return storage_class(nodes_uri, classes_uri)
- return ret
+
+def get_nodeinfo(storage_type, inventory_base_uri, nodes_uri, classes_uri, nodename):
+ nodes_uri, classes_uri = path_mangler(inventory_base_uri, nodes_uri,
+ classes_uri)
+ storage = get_storage(storage_type, nodes_uri, classes_uri)
+ # TODO: template interpolation
+ return storage.nodeinfo(nodename)
+
+
+def get_inventory(storage_type, inventory_base_uri, nodes_uri, classes_uri):
+ nodes_uri, classes_uri = path_mangler(inventory_base_uri, nodes_uri,
+ classes_uri)
+ storage = get_storage(storage_type, nodes_uri, classes_uri)
+ return storage.inventory()
+
def output(data, fmt, pretty_print=False):
output_class = OutputLoader(fmt).load()
diff --git a/reclass/adapters/__init__.py b/reclass/adapters/__init__.py
old mode 100644
new mode 100755
diff --git a/reclass/adapters/ansible.py b/reclass/adapters/ansible.py
old mode 100644
new mode 100755
index 057e730..404481a
--- a/reclass/adapters/ansible.py
+++ b/reclass/adapters/ansible.py
@@ -1,81 +1,62 @@
-#
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of reclass (http://github.com/madduck/reclass)
#
# IMPORTANT NOTICE: I was kicked out of the Ansible community, and therefore
# I have no interest in developing this adapter anymore. If you use it and
-# want to turn it into a setuptools entrypoints compatible adapter, I will
-# take your patch.
+# have changes, I will take your patch.
#
# Copyright © 2007–13 martin f. krafft <madduck@madduck.net>
# Released under the terms of the Artistic Licence 2.0
#
-import os, sys, posix, stat
-import reclass.config
-from reclass.errors import InvocationError, ReclassException
-from reclass import get_data, output
+import os, sys, posix, optparse
-def ansible_adapter(ansible_dir, exc_handler):
+from reclass import get_nodeinfo, get_inventory, output
+from reclass.errors import ReclassException
+from reclass.config import find_and_read_configfile, get_options
+from reclass.version import *
+from reclass.constants import MODE_NODEINFO
+
+def cli():
try:
- if len(sys.argv) == 1:
- raise InvocationError('Need to specify --list or --host.',
- posix.EX_USAGE)
+ # this adapter has to be symlinked to ansible_dir, so we can use this
+ # information to initialise the inventory_base_uri to ansible_dir:
+ ansible_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
- # The adapter resides in the Ansible directory, so let's look there
- # for an optional configuration file called reclass-config.yml.
- options = {'output': 'json',
- 'pretty_print': True,
- 'applications_postfix': '_hosts'
- }
- config_path = os.path.join(ansible_dir, 'reclass-config.yml')
- if os.path.exists(config_path) and os.access(config_path, os.R_OK):
- options.update(reclass.config.read_config_file(config_path))
+ defaults = {'inventory_base_uri': ansible_dir,
+ 'pretty_print' : True,
+ 'output' : 'json',
+ 'applications_postfix': '_hosts'
+ }
+ defaults.update(find_and_read_configfile())
- # Massage options into shape
- if 'storage_type' not in options:
- options['storage_type'] = 'yaml_fs'
+ def add_ansible_options_group(parser, defaults):
+ group = optparse.OptionGroup(parser, 'Ansible options',
+ 'Ansible-specific options')
+ group.add_option('--applications-postfix',
+ dest='applications_postfix',
+ default=defaults.get('applications_postfix'),
+ help='postfix to append to applications to '\
+ 'turn them into groups')
+ parser.add_option_group(group)
- if 'nodes_uri' not in options:
- nodes_uri = os.path.join(ansible_dir, 'nodes')
- if stat.S_ISDIR(os.stat(nodes_uri).st_mode):
- options['nodes_uri'] = nodes_uri
- else:
- raise InvocationError('nodes_uri not specified',
- posix.EX_USAGE)
+ options = get_options(RECLASS_NAME, VERSION, DESCRIPTION,
+ inventory_shortopt='-l',
+ inventory_longopt='--list',
+ inventory_help='output the inventory',
+ nodeinfo_shortopt='-t',
+ nodeinfo_longopt='--host',
+ nodeinfo_dest='hostname',
+ nodeinfo_help='output host_vars for the given host',
+ add_options_cb=add_ansible_options_group,
+ defaults=defaults)
- if 'classes_uri' not in options:
- classes_uri = os.path.join(ansible_dir, 'classes')
- if not stat.S_ISDIR(os.stat(classes_uri).st_mode):
- classes_uri = options['nodes_uri']
- options['classes_uri'] = classes_uri
-
- # Invoke reclass according to what Ansible wants.
- # If the 'node' option is set, we want node information. If the option
- # is False instead, we print the inventory. Yeah for option abuse!
- if sys.argv[1] == '--list':
- if len(sys.argv) > 2:
- raise InvocationError('Unknown arguments: ' + \
- ' '.join(sys.argv[2:]), posix.EX_USAGE)
- options['node'] = False
-
- elif sys.argv[1] == '--host':
- if len(sys.argv) < 3:
- raise InvocationError('Missing hostname.', posix.EX_USAGE)
- elif len(sys.argv) > 3:
- raise InvocationError('Unknown arguments: ' + \
- ' '.join(sys.argv[3:]), posix.EX_USAGE)
- options['node'] = sys.argv[2]
-
- else:
- raise InvocationError('Unknown mode (--list or --host required).',
- posix.EX_USAGE)
-
- data = get_data(options['storage_type'], options['nodes_uri'],
- options['classes_uri'], options['node'])
-
- if options['node']:
+ if options.mode == MODE_NODEINFO:
+ data = get_nodeinfo(options.storage_type,
+ options.inventory_base_uri, options.nodes_uri,
+ options.classes_uri, options.hostname)
# Massage and shift the data like Ansible wants it
data['parameters']['__reclass__'] = data['__reclass__']
for i in ('classes', 'applications'):
@@ -83,19 +64,27 @@
data = data['parameters']
else:
+ data = get_inventory(options.storage_type,
+ options.inventory_base_uri,
+ options.nodes_uri, options.classes_uri)
# 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']
apps = data['applications']
- if 'applications_postfix' in options:
- postfix = options['applications_postfix']
+ if options.applications_postfix:
+ postfix = options.applications_postfix
groups.update([(k + postfix, v) for k,v in apps.iteritems()])
else:
groups.update(apps)
data = groups
- print output(data, options['output'], options['pretty_print'])
+ print output(data, options.output, options.pretty_print)
except ReclassException, e:
- exc_handler(e.message, e.rc)
+ e.exit_with_message(sys.stderr)
+
+ sys.exit(posix.EX_OK)
+
+if __name__ == '__main__':
+ cli()
diff --git a/reclass/adapters/salt.py b/reclass/adapters/salt.py
old mode 100644
new mode 100755
index c3edc25..5c6890a
--- a/reclass/adapters/salt.py
+++ b/reclass/adapters/salt.py
@@ -1,4 +1,4 @@
-#
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of reclass (http://github.com/madduck/reclass)
@@ -6,54 +6,37 @@
# Copyright © 2007–13 martin f. krafft <madduck@madduck.net>
# Released under the terms of the Artistic Licence 2.0
#
-from reclass import config, get_data
-from reclass.errors import InvocationError
-def _check_storage_params(storage_type, inventory_base_uri, nodes_uri,
- classes_uri):
- nodes_uri, classes_uri = config.path_mangler(inventory_base_uri,
- nodes_uri, classes_uri)
+import os, sys, posix
- if nodes_uri is None:
- raise InvocationError('missing nodes_uri or inventory_base_uri parameters')
+from reclass import get_nodeinfo, get_inventory, output
+from reclass.errors import ReclassException
+from reclass.config import find_and_read_configfile, get_options
+from reclass.constants import MODE_NODEINFO
+from reclass.defaults import *
+from reclass.version import *
- if classes_uri is None:
- raise InvocationError('missing classes_uri or inventory_base_uri parameters')
+def ext_pillar(minion_id, pillar,
+ storage_type=OPT_STORAGE_TYPE,
+ inventory_base_uri=OPT_INVENTORY_BASE_URI,
+ nodes_uri=OPT_NODES_URI,
+ classes_uri=OPT_CLASSES_URI):
- if storage_type is None:
- storage_type = 'yaml_fs' # TODO: should be obtained from config
-
- return storage_type, nodes_uri, classes_uri
-
-
-def _get_data(storage_type, inventory_base_uri, nodes_uri, classes_uri, node):
-
- storage_type, nodes_uri, classes_uri = _check_storage_params(storage_type,
- inventory_base_uri,
- nodes_uri,
- classes_uri)
- return get_data(storage_type, nodes_uri, classes_uri, node)
-
-
-def ext_pillar(minion_id, pillar, storage_type=None, inventory_base_uri=None,
- nodes_uri=None, classes_uri=None):
-
- data = _get_data(storage_type, inventory_base_uri, nodes_uri, classes_uri,
- minion_id)
+ data = get_nodeinfo(storage_type, inventory_base_uri, nodes_uri,
+ classes_uri, minion_id)
params = data.get('parameters', {})
params['__reclass__'] = {}
params['__reclass__']['applications'] = data['applications']
params['__reclass__']['classes'] = data['classes']
-
- # TODO: template interpolation?
return params
-def top(storage_type=None, inventory_base_uri=None, nodes_uri=None,
- classes_uri=None):
+def top(storage_type=OPT_STORAGE_TYPE,
+ inventory_base_uri=OPT_INVENTORY_BASE_URI, nodes_uri=OPT_NODES_URI,
+ classes_uri=OPT_CLASSES_URI):
- data = _get_data(storage_type, inventory_base_uri, nodes_uri, classes_uri,
- node=None)
+ data = get_inventory(storage_type, inventory_base_uri, nodes_uri,
+ classes_uri)
env = 'base'
top = {env: {}}
# TODO: node environments
@@ -64,3 +47,42 @@
top[env][node_id] = node_data['applications']
return top
+
+
+def cli():
+ try:
+ defaults = {'pretty_print' : True,
+ 'output' : 'yaml'
+ }
+ defaults.update(find_and_read_configfile())
+ options = get_options(RECLASS_NAME, VERSION, DESCRIPTION,
+ inventory_shortopt='-t',
+ inventory_longopt='--top',
+ inventory_help='output the state tops (inventory)',
+ nodeinfo_shortopt='-p',
+ nodeinfo_longopt='--pillar',
+ nodeinfo_dest='nodename',
+ nodeinfo_help='output pillar data for a specific node',
+ defaults=defaults)
+
+ 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)
+ else:
+ data = top(storage_type=options.storage_type,
+ inventory_base_uri=options.inventory_base_uri,
+ nodes_uri=options.nodes_uri,
+ classes_uri=options.classes_uri)
+
+ print output(data, options.output, options.pretty_print)
+
+ except ReclassException, e:
+ e.exit_with_message(sys.stderr)
+
+ sys.exit(posix.EX_OK)
+
+if __name__ == '__main__':
+ cli()
diff --git a/reclass/cli.py b/reclass/cli.py
new file mode 100644
index 0000000..85ea949
--- /dev/null
+++ b/reclass/cli.py
@@ -0,0 +1,44 @@
+#
+# -*- 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 sys, os, posix
+
+from reclass import get_nodeinfo, get_inventory, output
+from reclass.config import find_and_read_configfile, get_options
+from reclass.errors import ReclassException
+from reclass.defaults import *
+from reclass.constants import MODE_NODEINFO
+from reclass.version import *
+
+def main():
+ try:
+ defaults = {'pretty_print' : OPT_PRETTY_PRINT,
+ 'output' : OPT_OUTPUT
+ }
+ defaults.update(find_and_read_configfile())
+ options = get_options(RECLASS_NAME, VERSION, DESCRIPTION,
+ defaults=defaults)
+ if options.mode == MODE_NODEINFO:
+ data = get_nodeinfo(options.storage_type,
+ options.inventory_base_uri, options.nodes_uri,
+ options.classes_uri, options.nodename)
+ else:
+ data = get_inventory(options.storage_type,
+ options.inventory_base_uri,
+ options.nodes_uri, options.classes_uri)
+
+ print output(data, options.output, options.pretty_print)
+
+ except ReclassException, e:
+ e.exit_with_message(sys.stderr)
+
+ sys.exit(posix.EX_OK)
+
+if __name__ == '__main__':
+ main()
diff --git a/reclass/config.py b/reclass/config.py
index 498e7d1..4d0dab4 100644
--- a/reclass/config.py
+++ b/reclass/config.py
@@ -6,81 +6,164 @@
# Copyright © 2007–13 martin f. krafft <madduck@madduck.net>
# Released under the terms of the Artistic Licence 2.0
#
+
import yaml, os, optparse, posix, sys
-def _make_parser(name, version, description, defaults={}):
+import errors
+from defaults import *
+from constants import MODE_NODEINFO, MODE_INVENTORY
+
+def make_db_options_group(parser, defaults={}):
+ ret = optparse.OptionGroup(parser, 'Database options',
+ 'Configure from where {0} collects data'.format(parser.prog))
+ ret.add_option('-s', '--storage-type', dest='storage_type',
+ default=defaults.get('storage_type', OPT_STORAGE_TYPE),
+ help='the type of storage backend to use [%default]')
+ ret.add_option('-b', '--inventory-base-uri', dest='inventory_base_uri',
+ default=defaults.get('inventory_base_uri',
+ OPT_INVENTORY_BASE_URI),
+ help='the base URI to prepend to nodes and classes [%default]'),
+ ret.add_option('-u', '--nodes-uri', dest='nodes_uri',
+ default=defaults.get('nodes_uri', OPT_NODES_URI),
+ help='the URI to the nodes storage [%default]'),
+ ret.add_option('-c', '--classes-uri', dest='classes_uri',
+ default=defaults.get('classes_uri', OPT_CLASSES_URI),
+ help='the URI to the classes storage [%default]')
+ return ret
+
+
+def make_output_options_group(parser, defaults={}):
+ ret = optparse.OptionGroup(parser, 'Output options',
+ 'Configure the way {0} prints data'.format(parser.prog))
+ ret.add_option('-o', '--output', dest='output',
+ default=defaults.get('output', OPT_OUTPUT),
+ help='output format (yaml or json) [%default]')
+ ret.add_option('-y', '--pretty-print', dest='pretty_print',
+ action="store_true",
+ default=defaults.get('pretty_print', OPT_PRETTY_PRINT),
+ help='try to make the output prettier [%default]')
+ return ret
+
+
+def make_modes_options_group(parser, inventory_shortopt, inventory_longopt,
+ inventory_help, nodeinfo_shortopt,
+ nodeinfo_longopt, nodeinfo_dest, nodeinfo_help):
+
+ def _mode_checker_cb(option, opt_str, value, parser):
+ if hasattr(parser.values, 'mode'):
+ raise optparse.OptionValueError('Cannot specify multiple modes')
+
+ if option == parser.get_option(nodeinfo_longopt):
+ setattr(parser.values, 'mode', MODE_NODEINFO)
+ setattr(parser.values, nodeinfo_dest, value)
+ else:
+ setattr(parser.values, 'mode', MODE_INVENTORY)
+ setattr(parser.values, nodeinfo_dest, None)
+
+ ret = optparse.OptionGroup(parser, 'Modes',
+ 'Specify one of these to determine what to do.')
+ ret.add_option(inventory_shortopt, inventory_longopt,
+ action='callback', callback=_mode_checker_cb,
+ help=inventory_help)
+ ret.add_option(nodeinfo_shortopt, nodeinfo_longopt,
+ default=None, dest=nodeinfo_dest, type='string',
+ action='callback', callback=_mode_checker_cb,
+ help=nodeinfo_help)
+ return ret
+
+
+def make_parser_and_checker(name, version, description,
+ inventory_shortopt='-i',
+ inventory_longopt='--inventory',
+ inventory_help='output the entire inventory',
+ nodeinfo_shortopt='-n',
+ nodeinfo_longopt='--nodeinfo',
+ nodeinfo_dest='nodename',
+ nodeinfo_help='output information for a specific node',
+ add_options_cb=None,
+ defaults={}):
+
parser = optparse.OptionParser(version=version)
parser.prog = name
parser.version = version
- parser.description = description
- parser.usage = '%prog [options] ( --inventory | --nodeinfo <nodename> )'
+ parser.description = description.capitalize()
+ parser.usage = '%prog [options] ( {0} | {1} {2} )'.format(inventory_longopt,
+ nodeinfo_longopt,
+ nodeinfo_dest.upper())
+ parser.epilog = 'Exactly one mode has to be specified.'
- options_group = optparse.OptionGroup(parser, 'Options',
- 'Configure the way {0} works'.format(name))
- options_group.add_option('-t', '--storage-type', dest='storage_type',
- default=defaults.get('storage_type', 'yaml_fs'),
- help='the type of storage backend to use [%default]')
- options_group.add_option('-b', '--inventory-base-uri', dest='inventory_base_uri',
- default=defaults.get('inventory_base_uri', None),
- help='the base URI to append to nodes and classes [%default]'),
- options_group.add_option('-u', '--nodes-uri', dest='nodes_uri',
- default=defaults.get('nodes_uri', './nodes'),
- help='the URI to the nodes storage [%default]'),
- options_group.add_option('-c', '--classes-uri', dest='classes_uri',
- default=defaults.get('classes_uri', './classes'),
- help='the URI to the classes storage [%default]')
- options_group.add_option('-o', '--output', dest='output',
- default=defaults.get('output', 'yaml'),
- help='output format (yaml or json) [%default]')
- options_group.add_option('-p', '--pretty-print', dest='pretty_print',
- default=defaults.get('pretty_print', False),
- action="store_true",
- help='try to make the output prettier [%default]')
- parser.add_option_group(options_group)
+ db_group = make_db_options_group(parser, defaults)
+ parser.add_option_group(db_group)
- run_modes = optparse.OptionGroup(parser, 'Modes',
- 'Specify one of these to determine what to do.')
- run_modes.add_option('-i', '--inventory', action='store_false', dest='node',
- help='output the entire inventory')
- run_modes.add_option('-n', '--nodeinfo', action='store', dest='node',
- default=None,
- help='output information for a specific node')
- parser.add_option_group(run_modes)
+ output_group = make_output_options_group(parser, defaults)
+ parser.add_option_group(output_group)
- return parser
+ if callable(add_options_cb):
+ add_options_cb(parser, defaults)
-def _parse_and_check_options(parser):
+ modes_group = make_modes_options_group(parser, inventory_shortopt,
+ inventory_longopt, inventory_help,
+ nodeinfo_shortopt,
+ nodeinfo_longopt, nodeinfo_dest,
+ nodeinfo_help)
+ parser.add_option_group(modes_group)
+
+ def option_checker(options, args):
+ if len(args) > 0:
+ parser.error('No arguments allowed')
+ elif not hasattr(options, 'mode') \
+ or options.mode not in (MODE_NODEINFO, MODE_INVENTORY):
+ parser.error('You need to specify exactly one mode '\
+ '({0} or {1})'.format(inventory_longopt,
+ nodeinfo_longopt))
+ elif options.mode == MODE_NODEINFO \
+ and not getattr(options, nodeinfo_dest, None):
+ parser.error('Mode {0} needs {1}'.format(nodeinfo_longopt,
+ nodeinfo_dest.upper()))
+ elif options.inventory_base_uri is None and options.nodes_uri is None:
+ parser.error('Must specify --inventory-base-uri or --nodes-uri')
+ elif options.inventory_base_uri is None and options.classes_uri is None:
+ parser.error('Must specify --inventory-base-uri or --classes-uri')
+
+ return parser, option_checker
+
+
+def get_options(name, version, description,
+ inventory_shortopt='-i',
+ inventory_longopt='--inventory',
+ inventory_help='output the entire inventory',
+ nodeinfo_shortopt='-n',
+ nodeinfo_longopt='--nodeinfo',
+ nodeinfo_dest='nodename',
+ nodeinfo_help='output information for a specific node',
+ add_options_cb=None,
+ defaults={}):
+
+ parser, checker = make_parser_and_checker(name, version, description,
+ inventory_shortopt,
+ inventory_longopt,
+ inventory_help,
+ nodeinfo_shortopt,
+ nodeinfo_longopt, nodeinfo_dest,
+ nodeinfo_help,
+ add_options_cb,
+ defaults=defaults)
options, args = parser.parse_args()
-
- def usage_error(msg):
- sys.stderr.write(msg + '\n\n')
- parser.print_help(sys.stderr)
- sys.exit(posix.EX_USAGE)
-
- if len(args) > 0:
- usage_error('No arguments allowed')
- elif options.node is None:
- usage_error('You need to either pass --inventory or --nodeinfo <nodename>')
- elif options.output not in ('json', 'yaml'):
- usage_error('Unknown output format: {0}'.format(options.output))
- elif options.inventory_base_uri is None and options.nodes_uri is None:
- usage_error('Must specify --inventory-base-uri or --nodes-uri')
- elif options.inventory_base_uri is None and options.classes_uri is None:
- usage_error('Must specify --inventory-base-uri or --classes-uri')
+ checker(options, args)
return options
-def read_config_file(path):
- if os.path.exists(path):
- return yaml.safe_load(file(path))
- else:
- return {}
-def get_options(name, version, description, config_file=None, defaults={}):
- if config_file is not None:
- defaults.update(read_config_file(config_file))
- parser = _make_parser(name, version, description, defaults)
- return _parse_and_check_options(parser)
+def find_and_read_configfile(filename=CONFIG_FILE_NAME,
+ dirs=CONFIG_FILE_SEARCH_PATH):
+ for d in dirs:
+ f = os.path.join(d, filename)
+ if os.access(f, os.R_OK):
+ return yaml.safe_load(file(f))
+ elif os.path.isfile(f):
+ raise PermissionsError('cannot read %s' % f)
+ return {}
+
def path_mangler(inventory_base_uri, nodes_uri, classes_uri):
diff --git a/reclass/constants.py b/reclass/constants.py
new file mode 100644
index 0000000..eac0bae
--- /dev/null
+++ b/reclass/constants.py
@@ -0,0 +1,13 @@
+#
+# -*- 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
+#
+
+class MODE_NODEINFO:
+ pass
+class MODE_INVENTORY:
+ pass
diff --git a/reclass/defaults.py b/reclass/defaults.py
new file mode 100644
index 0000000..39142a5
--- /dev/null
+++ b/reclass/defaults.py
@@ -0,0 +1,25 @@
+#
+# -*- 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 os, sys
+from version import RECLASS_NAME
+
+# defaults for the command-line options
+OPT_STORAGE_TYPE = 'yaml_fs'
+OPT_INVENTORY_BASE_URI = os.path.join('/etc', RECLASS_NAME)
+OPT_NODES_URI = 'nodes'
+OPT_CLASSES_URI = 'classes'
+OPT_PRETTY_PRINT = True
+OPT_OUTPUT = 'yaml'
+
+CONFIG_FILE_SEARCH_PATH = [os.getcwd(),
+ os.path.expanduser('~'),
+ OPT_INVENTORY_BASE_URI,
+ os.path.dirname(sys.argv[0])
+ ]
+CONFIG_FILE_NAME = RECLASS_NAME + '-config.yml'
diff --git a/reclass/main.py b/reclass/main.py
deleted file mode 100644
index 406dc24..0000000
--- a/reclass/main.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/python
-# -*- 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
-#
-from version import *
-
-import sys, os, posix
-import reclass.config
-from reclass.output import OutputLoader
-from reclass.storage import StorageBackendLoader
-import reclass.errors
-from reclass import get_data, output
-
-def _error(msg, rc):
- print >>sys.stderr, msg
- sys.exit(rc)
-
-def run():
- config_file = None
- for d in (os.getcwd(), os.path.dirname(sys.argv[0])):
- f = os.path.join(d, RECLASS_NAME + '-config.yml')
- if os.access(f, os.R_OK):
- config_file = f
- break
- try:
- defaults = { 'pretty_print' : True, 'output' : 'yaml' }
- options = reclass.config.get_options(RECLASS_NAME, VERSION, DESCRIPTION,
- config_file, defaults)
- nodes_uri, classes_uri = reclass.config.path_mangler(options.inventory_base_uri,
- options.nodes_uri,
- options.classes_uri)
- data = get_data(options.storage_type, nodes_uri, classes_uri,
- options.node)
- print output(data, options.output, options.pretty_print)
- sys.exit(posix.EX_OK)
-
- except reclass.errors.ReclassException, e:
- _error(e.message, e.rc)
diff --git a/setup.py b/setup.py
index 68901f3..4dd88a4 100644
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,11 @@
url = URL,
packages = find_packages(),
entry_points = {
- 'console_scripts': ['reclass = reclass.main:run' ],
+ 'console_scripts': [
+ 'reclass = reclass.cli:main',
+ 'reclass-salt = reclass.adapters.salt:cli',
+ 'reclass-ansible = reclass.adapters.ansible:cli'
+ ]
},
install_requires = ['pyyaml'],
setup_requires = ['nose'],