From b328bc023d1597917383bfe4cbc2923112419c81 Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Tue, 25 Aug 2015 10:40:58 +0530 Subject: [PATCH] Add a combine filter with documentation This is based on some code from (closed) PR #7872, but reworked based on suggestions by @abadger and the other core team members. Closes #7872 by @darkk (hash_merge/hash_replace filters) Closes #11153 by @telbizov (merged_dicts lookup plugin) --- docsite/rst/intro_configuration.rst | 5 +++++ docsite/rst/playbooks_filters.rst | 35 +++++++++++++++++++++++++++++ lib/ansible/plugins/filter/core.py | 19 ++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/docsite/rst/intro_configuration.rst b/docsite/rst/intro_configuration.rst index 2cf03a70d2d..e46d2c3f19c 100644 --- a/docsite/rst/intro_configuration.rst +++ b/docsite/rst/intro_configuration.rst @@ -334,6 +334,11 @@ official examples repos do not use this setting:: The valid values are either 'replace' (the default) or 'merge'. +.. versionadded: '2.0' + +If you want to merge hashes without changing the global settings, use +the `combine` filter described in :doc:`playbooks_filters`. + .. _hostfile: hostfile diff --git a/docsite/rst/playbooks_filters.rst b/docsite/rst/playbooks_filters.rst index 857fc770ba3..70cd908d9b1 100644 --- a/docsite/rst/playbooks_filters.rst +++ b/docsite/rst/playbooks_filters.rst @@ -316,6 +316,41 @@ To get a sha256 password hash with a specific salt:: Hash types available depend on the master system running ansible, 'hash' depends on hashlib password_hash depends on crypt. +.. _combine_filter: + +Combining hashes/dictionaries +----------------------------- + +.. versionadded:: 2.0 + +The `combine` filter allows hashes to be merged. For example, the +following would override keys in one hash: + + {{ {'a':1, 'b':2}|combine({'b':3}) }} + +The resulting hash would be: + + {'a':1, 'b':3} + +The filter also accepts an optional `recursive=True` parameter to not +only override keys in the first hash, but also recurse into nested +hashes and merge their keys too: + + {{ {'a':{'foo':1, 'bar':2}, 'b':2}|combine({'a':{'bar':3, 'baz':4}}, recursive=True) }} + +This would result in: + + {'a':{'foo':1, 'bar':3, 'baz':4}, 'b':2} + +The filter can also take multiple arguments to merge: + + {{ a|combine(b, c, d) }} + +In this case, keys in `d` would override those in `c`, which would +override those in `b`, and so on. + +This behaviour does not depend on the value of the `hash_behaviour` +setting in `ansible.cfg`. .. _other_useful_filters: diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index 84e055b61f8..aa7464c3231 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -19,6 +19,7 @@ from __future__ import absolute_import import sys import base64 +import itertools import json import os.path import ntpath @@ -42,6 +43,7 @@ from ansible import errors from ansible.parsing.yaml.dumper import AnsibleDumper from ansible.utils.hashing import md5s, checksum_s from ansible.utils.unicode import unicode_wrap, to_unicode +from ansible.utils.vars import merge_hash try: import passlib.hash @@ -231,6 +233,20 @@ def mandatory(a): raise errors.AnsibleFilterError('Mandatory variable not defined.') return a +def combine(*terms, **kwargs): + recursive = kwargs.get('recursive', False) + if len(kwargs) > 1 or (len(kwargs) == 1 and 'recursive' not in kwargs): + raise errors.AnsibleFilterError("'recursive' is the only valid keyword argument") + + for t in terms: + if not isinstance(t, dict): + raise errors.AnsibleFilterError("|combine expects dictionaries, got " + repr(t)) + + if recursive: + return reduce(merge_hash, terms) + else: + return dict(itertools.chain(*map(dict.iteritems, terms))) + class FilterModule(object): ''' Ansible core jinja2 filters ''' @@ -300,4 +316,7 @@ class FilterModule(object): 'shuffle': randomize_list, # undefined 'mandatory': mandatory, + + # merge dicts + 'combine': combine, }