diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bede1e637..3ab6e21f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Core Features * a new chroot connection type * module common code now has basic type checking (and casting) capability * module common now supports a 'no_log' attribute to mark a field as not to be syslogged +* inventory can now point to a directory containing multiple scripts/hosts files Modules Added: diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py index 32c757d282..9efb27b665 100644 --- a/lib/ansible/inventory/__init__.py +++ b/lib/ansible/inventory/__init__.py @@ -25,6 +25,7 @@ import subprocess import ansible.constants as C from ansible.inventory.ini import InventoryParser from ansible.inventory.script import InventoryScript +from ansible.inventory.dir import InventoryDirectory from ansible.inventory.group import Group from ansible.inventory.host import Host from ansible import errors @@ -35,7 +36,7 @@ class Inventory(object): Host inventory for ansible. """ - __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script', + __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list'] def __init__(self, host_list=C.DEFAULT_HOST_LIST): @@ -60,17 +61,11 @@ class Inventory(object): self._also_restriction = None self._subset = None - # whether the inventory file is a script - self._is_script = False - if type(host_list) in [ str, unicode ]: if host_list.find(",") != -1: host_list = host_list.split(",") host_list = [ h for h in host_list if h and h.strip() ] - else: - utils.plugins.vars_loader.add_directory(self.basedir()) - if type(host_list) == list: all = Group('all') self.groups = [ all ] @@ -81,8 +76,12 @@ class Inventory(object): else: all.add_host(Host(x)) elif os.path.exists(host_list): - if utils.is_executable(host_list): - self._is_script = True + if os.path.isdir(host_list): + # Ensure basedir is inside the directory + self.host_list = os.path.join(self.host_list, "") + self.parser = InventoryDirectory(filename=host_list) + self.groups = self.parser.groups.values() + elif utils.is_executable(host_list): self.parser = InventoryScript(filename=host_list) self.groups = self.parser.groups.values() else: @@ -92,6 +91,8 @@ class Inventory(object): self.groups = self.parser.groups.values() else: raise errors.AnsibleError("YAML inventory support is deprecated in 0.6 and removed in 0.7, see the migration script in examples/scripts in the git checkout") + + utils.plugins.vars_loader.add_directory(self.basedir(), with_subdir=True) else: raise errors.AnsibleError("Unable to find an inventory file, specify one with -i ?") @@ -280,16 +281,7 @@ class Inventory(object): vars.update(updated) vars.update(host.get_variables()) - if self._is_script: - cmd = [self.host_list,"--host",hostname] - try: - sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError, e: - raise errors.AnsibleError("problem running %s (%s)" % (' '.join(cmd), e)) - (out, err) = sp.communicate() - results = utils.parse_json(out) - - vars.update(results) + vars.update(self.parser.get_host_variables(host)) return vars def add_group(self, group): diff --git a/lib/ansible/inventory/dir.py b/lib/ansible/inventory/dir.py new file mode 100644 index 0000000000..b92ef4072d --- /dev/null +++ b/lib/ansible/inventory/dir.py @@ -0,0 +1,76 @@ +# (c) 2013, Daniel Hokka Zakrisson +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +############################################# + +import os +import ansible.constants as C +from ansible.inventory.host import Host +from ansible.inventory.group import Group +from ansible.inventory.ini import InventoryParser +from ansible.inventory.script import InventoryScript +from ansible import utils +from ansible import errors + +class InventoryDirectory(object): + ''' Host inventory parser for ansible using a directory of inventories. ''' + + def __init__(self, filename=C.DEFAULT_HOST_LIST): + self.names = os.listdir(filename) + self.names.sort() + self.directory = filename + self.parsers = [] + self.hosts = {} + self.groups = {} + for i in self.names: + if i.endswith("~") or i.endswith(".orig") or i.endswith(".bak"): + continue + # These are things inside of an inventory basedir + if i in ("host_vars", "group_vars", "vars_plugins"): + continue + fullpath = os.path.join(self.directory, i) + if os.path.isdir(fullpath): + parser = InventoryDirectory(filename=fullpath) + elif utils.is_executable(fullpath): + parser = InventoryScript(filename=fullpath) + else: + parser = InventoryParser(filename=fullpath) + self.parsers.append(parser) + # This takes a lot of code because we can't directly use any of the objects, as they have to blend + for name, group in parser.groups.iteritems(): + if name not in self.groups: + self.groups[name] = Group(name) + for k, v in group.get_variables().iteritems(): + self.groups[name].set_variable(k, v) + for host in group.get_hosts(): + if host.name not in self.hosts: + self.hosts[host.name] = Host(host.name) + for k, v in host.get_variables().iteritems(): + self.hosts[host.name].set_variable(k, v) + self.groups[name].add_host(self.hosts[host.name]) + # This needs to be a second loop to ensure all the parent groups exist + for name, group in parser.groups.iteritems(): + for ancestor in group.get_ancestors(): + self.groups[ancestor.name].add_child_group(self.groups[name]) + + def get_host_variables(self, host): + """ Gets additional host variables from all inventories """ + vars = {} + for i in self.parsers: + vars.update(i.get_host_variables(host)) + return vars + diff --git a/lib/ansible/inventory/ini.py b/lib/ansible/inventory/ini.py index d79a148894..d05037bc0b 100644 --- a/lib/ansible/inventory/ini.py +++ b/lib/ansible/inventory/ini.py @@ -173,3 +173,6 @@ class InventoryParser(object): group.set_variable(k, re.sub(r"^['\"]|['\"]$", '', v)) else: group.set_variable(k, v) + + def get_host_variables(self, host): + return {} diff --git a/lib/ansible/inventory/script.py b/lib/ansible/inventory/script.py index 44d3f37b2b..628e802934 100644 --- a/lib/ansible/inventory/script.py +++ b/lib/ansible/inventory/script.py @@ -29,7 +29,8 @@ class InventoryScript(object): def __init__(self, filename=C.DEFAULT_HOST_LIST): - cmd = [ filename, "--list" ] + self.filename = filename + cmd = [ self.filename, "--list" ] try: sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError, e: @@ -77,3 +78,13 @@ class InventoryScript(object): if child_name in groups: groups[group_name].add_child_group(groups[child_name]) return groups + + def get_host_variables(self, host): + """ Runs