Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

#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. https://github.com/ansible/ansible/issues/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

This comment has been minimized.

Copy link

jheller commented Jun 21, 2016

Has this been tested with 2.1?

@razic

This comment has been minimized.

Copy link

razic commented Sep 1, 2016

this doesn't work with 2.1 😢

@eugene0707

This comment has been minimized.

Copy link

eugene0707 commented Nov 15, 2016

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
You can’t perform that action at this time.