From 8cf1815867e77d61b0ff66e6fb7835914b0e3bca Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Sat, 28 Nov 2015 20:09:20 +0530 Subject: [PATCH] Add an 'extract' filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At its most basic, this is nothing more than an array or hash lookup, but when used in conjunction with map, it is very useful. For example, while constructing an "ssh-keyscan …" command to update known_hosts on all hosts in a group, one can get a list of IP addresses with: groups['x']|map('extract', hostvars, 'ec2_ip_address')|list This returns hostvars[a].ec2_ip_address, hostvars[b].ec2_ip_address, and so on. You can even specify an array of keys for a recursive lookup, and mix string and integer keys depending on what you're looking up: ['localhost']|map('extract', hostvars, ['vars','group_names',0])|first == hostvars['localhost']['vars']['group_names'][0] == 'ungrouped' Includes documentation and tests. --- docsite/rst/playbooks_filters.rst | 33 +++++++++++++++++++ lib/ansible/plugins/filter/core.py | 15 +++++++++ .../roles/test_filters/tasks/main.yml | 10 ++++++ 3 files changed, 58 insertions(+) diff --git a/docsite/rst/playbooks_filters.rst b/docsite/rst/playbooks_filters.rst index 7d4ace9c4b..830448655a 100644 --- a/docsite/rst/playbooks_filters.rst +++ b/docsite/rst/playbooks_filters.rst @@ -352,6 +352,39 @@ override those in `b`, and so on. This behaviour does not depend on the value of the `hash_behaviour` setting in `ansible.cfg`. +.. _extract_filter: + +Extracting values from containers +--------------------------------- + +.. versionadded:: 2.0 + +The `extract` filter is used to map from a list of indices to a list of +values from a container (hash or array):: + + {{ [0,2]|map('extract', ['x','y','z'])|list }} + {{ ['x','y']|map('extract', {'x': 42, 'y': 31})|list }} + +The results of the above expressions would be:: + + ['x', 'z'] + [42, 31] + +The filter can take another argument:: + + {{ groups['x']|map('extract', hostvars, 'ec2_ip_address')|list }} + +This takes the list of hosts in group 'x', looks them up in `hostvars`, +and then looks up the `ec2_ip_address` of the result. The final result +is a list of IP addresses for the hosts in group 'x'. + +The third argument to the filter can also be a list, for a recursive +lookup inside the container:: + + {{ ['a']|map('extract', b, ['x','y'])|list }} + +This would return a list containing the value of `b['a']['x']['y']`. + .. _comment_filter: Comment Filter diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index d5e1a12e53..3ab9db5a51 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -339,6 +339,18 @@ def comment(text, style='plain', **kw): str_postfix, str_end) +def extract(item, container, morekeys=None): + from jinja2.runtime import Undefined + + value = container[item] + + if value is not Undefined and morekeys is not None: + if not isinstance(morekeys, list): + morekeys = [morekeys] + + value = reduce(lambda d, k: d[k], morekeys, value) + + return value class FilterModule(object): ''' Ansible core jinja2 filters ''' @@ -415,4 +427,7 @@ class FilterModule(object): # comment-style decoration 'comment': comment, + + # array and dict lookups + 'extract': extract, } diff --git a/test/integration/roles/test_filters/tasks/main.yml b/test/integration/roles/test_filters/tasks/main.yml index a94bb8c655..af6c5d49de 100644 --- a/test/integration/roles/test_filters/tasks/main.yml +++ b/test/integration/roles/test_filters/tasks/main.yml @@ -68,3 +68,13 @@ - '"0.10 GB" == 102400000|human_readable(unit="G")' - '"0.10 Gb" == 102400000|human_readable(isbits=True, unit="G")' +- name: Container lookups with extract + assert: + that: + - "'x' == [0]|map('extract',['x','y'])|list|first" + - "'y' == [1]|map('extract',['x','y'])|list|first" + - "42 == ['x']|map('extract',{'x':42,'y':31})|list|first" + - "31 == ['x','y']|map('extract',{'x':42,'y':31})|list|last" + - "'local' == ['localhost']|map('extract',hostvars,'ansible_connection')|list|first" + - "'local' == ['localhost']|map('extract',hostvars,['ansible_connection'])|list|first" + - "'ungrouped' == ['localhost']|map('extract',hostvars,['vars','group_names',0])|list|first"