From 6cd3ba5b06578deda4aca19e77debb59d1d7df49 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 1 Jun 2013 10:38:16 -0400 Subject: [PATCH] Allow the group_vars and host_vars directories to be stored alongside the playbook as well as inventory. --- bin/ansible-playbook | 3 + lib/ansible/inventory/__init__.py | 18 +++- .../inventory/vars_plugins/group_vars.py | 87 ++++++++++++++----- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index f728a278d42..b674000bf2f 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -113,6 +113,9 @@ def main(args): # run all playbooks specified on the command line for playbook in args: + # let inventory know which playbooks are using so it can know the basedirs + inventory.set_playbook_basedir(os.path.dirname(playbook)) + stats = callbacks.AggregateStats() playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY) if options.step: diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py index 55d8e0b06e3..460b63053ad 100644 --- a/lib/ansible/inventory/__init__.py +++ b/lib/ansible/inventory/__init__.py @@ -38,7 +38,7 @@ class Inventory(object): __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list', - '_vars_plugins'] + '_vars_plugins', '_playbook_basedir'] def __init__(self, host_list=C.DEFAULT_HOST_LIST): @@ -54,6 +54,9 @@ class Inventory(object): self._hosts_cache = {} self._groups_list = {} + # to be set by calling set_playbook_basedir by ansible-playbook + self._playbook_basedir = None + # the inventory object holds a list of groups self.groups = [] @@ -372,3 +375,16 @@ class Inventory(object): if not self.is_file(): return None return os.path.dirname(self.host_list) + + def playbook_basedir(self): + """ returns the directory of the current playbook """ + return self._playbook_basedir + + def set_playbook_basedir(self, dir): + """ + sets the base directory of the playbook so inventory plugins can use it to find + variable files and other things. + """ + self._playbook_basedir = dir + + diff --git a/lib/ansible/inventory/vars_plugins/group_vars.py b/lib/ansible/inventory/vars_plugins/group_vars.py index d07c90ce581..405ee50f91d 100644 --- a/lib/ansible/inventory/vars_plugins/group_vars.py +++ b/lib/ansible/inventory/vars_plugins/group_vars.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan +# (c) 2012-2013, Michael DeHaan # # This file is part of Ansible # @@ -23,45 +23,90 @@ import ansible.constants as C class VarsModule(object): + """ + Loads variables from group_vars/ and host_vars/ in directories parallel + to the inventory base directory or in the same directory as the playbook. Variables in the playbook + dir will win over the inventory dir if files are in both. + """ + def __init__(self, inventory): + + """ constructor """ + self.inventory = inventory def run(self, host): - # return the inventory variables for the host + + """ main body of the plugin, does actual loading """ inventory = self.inventory - #hostrec = inventory.get_host(host) + self.pb_basedir = inventory.playbook_basedir() + # sort groups by depth so deepest groups can override the less deep ones groupz = sorted(inventory.groups_for_host(host.name), key=lambda g: g.depth) groups = [ g.name for g in groupz ] - basedir = inventory.basedir() - - if basedir is None: - # could happen when inventory is passed in via the API - return + inventory_basedir = inventory.basedir() results = {} + scan_pass = 0 + + # look in both the inventory base directory and the playbook base directory + for basedir in [ inventory_basedir, self.pb_basedir ]: + + + # this can happen from particular API usages, particularly if not run + # from /usr/bin/ansible-playbook + if basedir is None: + continue + + scan_pass = scan_pass + 1 + + # it's not an eror if the directory does not exist, keep moving + if not os.path.exists(basedir): + continue + + # save work of second scan if the directories are the same + if inventory_basedir == self.pb_basedir and scan_pass != 1: + continue + + # load vars in dir/group_vars/name_of_group + for x in groups: - # load vars in inventory_dir/group_vars/name_of_group - for x in groups: - p = os.path.join(basedir, "group_vars/%s" % x) + p = os.path.join(basedir, "group_vars/%s" % x) + + # the file can be or end in .yml or .yaml + # currently ALL will be loaded, even if more than one + paths = [p, '.'.join([p, 'yml']), '.'.join([p, 'yaml'])] + + for path in paths: + + if os.path.exists(path) and not os.path.isdir(path): + data = utils.parse_yaml_from_file(path) + if type(data) != dict: + raise errors.AnsibleError("%s must be stored as a dictionary/hash" % path) + + # combine vars overrides by default but can be configured to do a hash + # merge in settings + + results = utils.combine_vars(results, data) + + # group vars have been loaded + # load vars in inventory_dir/hosts_vars/name_of_host + # these have greater precedence than group variables + + p = os.path.join(basedir, "host_vars/%s" % host.name) + + # again allow the file to be named filename or end in .yml or .yaml paths = [p, '.'.join([p, 'yml']), '.'.join([p, 'yaml'])] + for path in paths: + if os.path.exists(path) and not os.path.isdir(path): data = utils.parse_yaml_from_file(path) if type(data) != dict: raise errors.AnsibleError("%s must be stored as a dictionary/hash" % path) results = utils.combine_vars(results, data) - # load vars in inventory_dir/hosts_vars/name_of_host - p = os.path.join(basedir, "host_vars/%s" % host.name) - paths = [p, '.'.join([p, 'yml']), '.'.join([p, 'yaml'])] - for path in paths: - if os.path.exists(path) and not os.path.isdir(path): - data = utils.parse_yaml_from_file(path) - if type(data) != dict: - raise errors.AnsibleError("%s must be stored as a dictionary/hash" % path) - results = utils.combine_vars(results, data) - + # all done, results is a dictionary of variables for this particular host. return results