Skip to content

Instantly share code, notes, and snippets.

@alexandrem
Forked from udondan/include_vars_merged
Last active November 15, 2016 21:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexandrem/50c42da4345c4decef1c to your computer and use it in GitHub Desktop.
Save alexandrem/50c42da4345c4decef1c to your computer and use it in GitHub Desktop.

#Why? Current version of Ansible (1.7.1) does not merge hashes in the include_vars task even if told so via hash_behaviour = merge in your ansible.cfg. ansible/ansible#9116

This action plugin will merge hashes no matter how you have configured your hash_behaviour.

#Compatibility Tested on Ansible 1.9.0.1

#Updates

  • Now works both for explicit inventory hosts and aliases (e.g ansible_ssh_host)

#How to setup: Save include_vars_merged.py to library/plugins/action/include_vars_merged.py

Save include_vars_merged to library/custom/include_vars_merged

The library folder needs to be on the same level as your playbook

#Why do you buffer the content in the runner object? Facts are merged in the runner and not in the plugin. You can access and modify those facts in the plugin when you call include_vars_merged for each file in a separate task, like so:

- include_vars_merged: /path/to/file-A.yml

- include_vars_merged: /path/to/file-B.yml

Though when called in a loop (e.g. with) each task is executed in parallel and at execution time these facts are not yet available. You can't merge what you can't access. Therefore the facts are additionally buffered in the parents runner object.

- include_vars_merged: /path/to/{{ item }}
  with:
    - file-A.yml
    - file-B.yml
# -*- mode: python -*-
# 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 <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
author: Daniel Schroeder
author_2: Alexandre Mclean
module: include_vars_merged
short_description: Load and merge variables from multiple files, dynamically within a task.
description:
- Loads variables from multiple YAML files dynamically during task runtime.
options:
free-form:
description:
- The file name from which variables should be loaded, if called from a role it will look for
the file in vars/ subdirectory of the role, otherwise the path would be relative to playbook. An absolute path can also be provided.
required: true
'''
EXAMPLES = """
# Conditionally decide to load in variables when x is 0, otherwise do not.
- include_vars_merged: contingency_plan.yml
when: x == 0
# Load a variable file based on the OS type, or a default if not found.
- include_vars_merged: "{{ item }}"
with_first_found:
- "{{ ansible_distribution }}.yml"
- "{{ ansible_os_family }}.yml"
- "default.yml"
"""
#!/usr/bin/env python
import os
from ansible.utils import template
from ansible import utils
from ansible import errors
from ansible.runner.return_data import ReturnData
class ActionModule(object):
TRANSFERS_FILES = False
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, module_args, inject, complex_args=None, **kwargs):
if not module_args:
result = dict(failed=True, msg="No source file given")
return ReturnData(conn=conn, comm_ok=True, result=result)
source = template.template(self.runner.basedir, module_args, inject)
if '_original_file' in inject:
source = utils.path_dwim_relative(inject['_original_file'], 'vars', source, self.runner.basedir)
else:
source = utils.path_dwim(self.runner.basedir, source)
if os.path.exists(source):
data = utils.parse_yaml_from_file(source, vault_password=self.runner.vault_pass)
if data and type(data) != dict:
raise errors.AnsibleError("%s must be stored as a dictionary/hash" % source)
elif data is None:
data = {}
else:
# check for cached vars from inventory hosts/aliases
host_to_names = dict((host.get_variables().get('ansible_ssh_host', conn.host), host.name)
for host in self.runner.inventory.get_hosts())
inventory_vars = conn.runner.setup_cache.get(host_to_names.get(conn.host, ''), {})
data = utils.merge_hash(inventory_vars, data)
if not hasattr(conn.runner, 'mergeBuffer'):
conn.runner.mergeBuffer = {}
if conn.host in conn.runner.mergeBuffer:
data = utils.merge_hash(conn.runner.mergeBuffer[conn.host], data)
conn.runner.mergeBuffer[conn.host] = data
result = dict(ansible_facts=data)
return ReturnData(conn=conn, comm_ok=True, result=result)
else:
result = dict(failed=True, msg="Source file not found.", file=source)
return ReturnData(conn=conn, comm_ok=True, result=result)
@jheller
Copy link

jheller commented Jun 21, 2016

Has this been tested with 2.1?

@razic
Copy link

razic commented Sep 1, 2016

this doesn't work with 2.1 😢

@eugene0707
Copy link

this role https://galaxy.ansible.com/eugene0707/merge_custom_vars/ work with Ansible 2+ and merge dictionaries from array of vars files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment