24

I would like to build an output that shows the key and value of a variable.

The following works perfectly ...

# Format in Ansible

msg="{{ php_command_result.results | map(attribute='item') | join(', ') }}"

# Output
{'value': {'svn_tag': '20150703r1_6.36_homeland'}, 'key': 'ui'}, {'value': {'svn_tag': '20150702r1_6.36_homeland'}, 'key': 'api'}

What I would like is to show the key and svn_tag together as so:

I'm able to display either the key or svn_tag but getting them to go together doesn't work.

msg="{{ php_command_result.results | map(attribute='item.key') | join(', ') }}"

# Output
ui, api

However, this is what I want.

# Desired Output
api - 20150702r1_6.36_homeland
ui - 20150703r1_6.36_homeland
Cœur
  • 37,241
  • 25
  • 195
  • 267
sdot257
  • 10,046
  • 26
  • 88
  • 122
  • I don't think this is possible with the default filters. But you can create your own filter plugins. http://docs.ansible.com/ansible/developing_plugins.html#filter-plugins There is not much documentation though and you will need to look at the existing filter plugins to see how to implement it. – udondan Jul 29 '15 at 07:24

5 Answers5

14

Using Jinja statements:

- set_fact:
    php_command_result:
      results: [{"value":{"svn_tag":"20150703r1_6.36_homeland"},"key":"ui"},{"value":{"svn_tag":"20150702r1_6.36_homeland"},"key":"api"}]

- debug:
    msg: "{% for result in php_command_result.results %}\
        {{ result.key }} - {{ result.value.svn_tag }} |
      {% endfor %}"

Outputs:

ok: [localhost] => {
    "msg": "ui - 20150703r1_6.36_homeland | api - 20150702r1_6.36_homeland | "
}

If you want the results on separate lines:

- debug:
    msg: "{% set output = [] %}\
        {% for result in php_command_result.results %}\
          {{ output.append( result.key ~ ' - ' ~ result.value.svn_tag) }}\
        {% endfor %}\
      {{ output }}"

Outputs:

ok: [localhost] => {
    "msg": [
        "ui - 20150703r1_6.36_homeland", 
        "api - 20150702r1_6.36_homeland"
    ]
}

Either of these can be put on one line if desired:

- debug:
    msg: "{% for result in php_command_result.results %}{{ result.key }} - {{ result.value.svn_tag }} | {% endfor %}"

- debug:
    msg: "{% set output = [] %}{% for result in php_command_result.results %}{{ output.append( result.key ~ ' - ' ~ result.value.svn_tag) }}{% endfor %}{{ output }}"
bmaupin
  • 14,427
  • 5
  • 89
  • 94
  • 1
    To "compel" the output in to strings, in the case when your value is a float/int, you want to use `~` instead of `+` in your `output.append` concatenation. **Example:** `{{ output.append( result.key ~ ' - ' ~ result.value.svn_tag) }}`. See [Other Operators](http://jinja.pocoo.org/docs/dev/templates/#other-operators) - and thanks for the initial pointer... stupid [Ansible Bug/Feature](https://github.com/ansible/ansible/issues/5564). – RVT Jul 14 '18 at 02:35
  • Great, thank you! Worth nothing that the last option (with `output.append()`) actually generates a list, not a string, which is what I wanted. – EM0 Feb 22 '19 at 13:15
8

Here is solution without custom filter_plugin or running shell command. However, it requires additional fact to be set in a with_items loop(php_fmt).

- hosts: localhost
  connection: local
  gather_facts: false 
  tasks:
    - set_fact: 
        php_command_result:
          results: '[{"value":{"svn_tag":"20150703r1_6.36_homeland"},"key":"ui"},{"value":{"svn_tag":"20150702r1_6.36_homeland"},"key":"api"}]'

    - set_fact:
        php_fmt: "{{ php_fmt|default([])|union([item.key+' -- '+item.value.svn_tag ]) }}"
      with_items: "{{ php_command_result.results }}"

    - debug: 
        msg: "{{php_fmt|join(',')}}"
KrapivchenkoN
  • 81
  • 1
  • 4
  • 1
    I believe this is the simpler and best approach. Also discussed here: https://stackoverflow.com/a/57270885/3178743 – Frock81 Aug 12 '20 at 20:26
  • if `php_command_result.results` has many items, ansible will print the state of `php_fmt` variable for each item. That's too many lines. – Shiplu Mokaddim Jan 06 '21 at 18:09
6

I found this question when I searched for a way to handle a similar task, but I wanted the output to be another JSON list with the individual elements. It took me many diversions over map, filter and even Jinja2 for loops, so I will post the answer here too. Just create the following file and run via ansible-playbook:

- hosts: localhost
  connection: local
  vars:
    to_test: [ {'value': {'svn_tag': '20150703r1_6.36_homeland'}, 'key': 'ui'}, {'value': {'svn_tag': '20150702r1_6.36_homeland'}, 'key': 'api'} ]
  tasks:
  - debug:
      msg: "to_test: {{ to_test }}"
  - debug:
      msg: "to_test reduced: {{ to_test | json_query('[].{key: key, svn_tag: value.svn_tag}') | list }}"
  - debug:
      msg: "to_test reduced: {{ to_test | json_query(query1) | list }}"
    vars:
      query1: "[].{key: key, svn_tag: value.svn_tag}"

The end result is a nice JSON array:

[{'key': 'ui', 'svn_tag': '20150703r1_6.36_homeland'}, {'key': 'api', 'svn_tag': '20150702r1_6.36_homeland'}]"
calloc_org
  • 564
  • 5
  • 16
5

Here is another answer using filter_plugins which I found very easy to use.

If anyone still needs this you can use the following code (put in playbooks/filter_plugins/mapattributes.py):

#!/usr/bin/env python
class FilterModule(object):
  def filters(self):
    return { 'mapattributes': self.mapattributes }

  def mapattributes(self, list_of_dicts, list_of_keys):
    l = []
    for di in list_of_dicts:
      newdi = { }
      for key in list_of_keys:
        # newdi[key] = di[key]
        if di.get(key, None) != None:
          newdi[key] = di[key]

      l.append(newdi)
    return l

And let say you have this list which you only need to keys from:

    INTERFACES:
      - { name: GigabitEthernet1/0/24 , enabled: yes, state: up , description: FRONT }
      - { name: GigabitEthernet1/0/9  , enabled: yes, state: up , description: BACK  }

Let's create another variable filtering only the needed key:values

    test: "{{ INTERFACES | mapattributes(['name', 'description']) }}"

Test the output

    - debug:
        var: test
ok: [R1] => {
    "test": [
        {
            "description": "FRONT",
            "name": "GigabitEthernet1/0/24"
        },
        {
            "description": "BACK",
            "name": "GigabitEthernet1/0/9"
        }
    ]
}

This allow me to have a big dictionary and only slice the keys i need to pass to ios_interface/aggregate

Thx to Nee6ione, i found it on pallets/jinja github issue

Rabin
  • 826
  • 10
  • 21
4

You can do it by using the following techniques:

  1. Create filter_plugin. Add filter_plugins = <path to the folder> in ansible.cfg. Then create a file say my_plugin.py:

    class FilterModule(object):
    ''' Custom filter '''
        def filters(self, my_arg):
           return <parse it here......>
    

Example:

playbook.yml

---
- hosts: localhost
  gather_facts: no
  connection: local
  tasks:
    - set_fact: 
        php_command_result:
          results: {'value': {'svn_tag': '20150703r1_6.36_homeland'}, 'key': 'ui'}
    - debug: msg="Hey look what I got '{{ php_command_result.results | a }}'"

my_plugin.py

import json

class FilterModule(object):
    def filters(self):
      return {'a': a}

def a(a):
  r = '%s - %s' % (a['key'], a['value']['svn_tag'])
  return r
  1. Fast and easy approach: just use python/php/shell or what ever you prefer with shell module. Something like this:

    - name: Pars output
      shell: python -c "import json; json.loads('{{ php_command_result.results }}') ....
    
Vor
  • 33,215
  • 43
  • 135
  • 193