Search code examples
ansiblejinja2

Is there any way to loop through Ansible list of dictionary registered variable in combination with Jinja2?


In my inventory I have 3 servers in a group. I want to be able to increase that size in the future so I can add more nodes into the network and generate the template with Jinja2.

- name: Gathering API results
  shell: 
    cmd: "curl {{ groups['nodes'][node_index] }}/whatever/api/result "
  loop: "{{ groups['nodes'] }}"
  loop_control:
    index_var: node_index
  register: api_value

If I run some debug tasks hardcoding which list I want to use everyhing works fine

- debug: "msg={{ api_value.results.0.stdout }}"
- debug: "msg={{ api_value.results.1.stdout }}"
- debug: "msg={{ api_value.results.2.stdout }}"

output:

ok: [server-1] => {
        "msg": "random-value-a"
ok: [server-2] => {
        "msg": "random-value-b"    
ok: [server-3] => {
        "msg": "random-value-c"

The problem is when I try to increase the list number in Jinja template. I tried several for loops combination, nested for loops and many other things but nothing seems to be working.

For example I want my Jinja template look similar like this:

{% for vm in groups['nodes'] %}
NODE_{{ loop.index }}={{ api_value.results.{loop.index}.stdout }}
{% endfor %}

This way I want to achieve this output:

NODE_0=random-value-a
NODE_1=random-value-b
NODE_2=random-value-c

Is there any other way to workaround this? Or maybe is something I could do better in the "Gathering API results" task?


Solution

  • Given the inventory

    shell> cat hosts
    [nodes]
    server-1
    server-2
    server-3
    
    1. Either run it in the loop at a single host, e.g.
    - hosts: localhost
      gather_facts: false
      vars:
        whatever_api_result:
          server-1: random-value-a
          server-2: random-value-b
          server-3: random-value-c
      tasks:
        - command: "echo {{ whatever_api_result[item] }}"
          register: api_value
          loop: "{{ groups.nodes }}"
        - debug:
            msg: "{{ api_value.results|json_query('[].[item, stdout]') }}"
    

    gives

      msg:
      - - server-1
        - random-value-a
      - - server-2
        - random-value-b
      - - server-3
        - random-value-c
    

    Then, in the Jinja template, fix the index variable

        - debug:
            msg: |-
              {% for vm in groups.nodes %}
              NODE_{{ loop.index0 }}={{ api_value.results[loop.index0].stdout }}
              {% endfor %}
    

    gives what you want

      msg: |-
        NODE_0=random-value-a
        NODE_1=random-value-b
        NODE_2=random-value-c
    

    Optionally, iterate api_value.results. This gives the same result

        - debug:
            msg: |-
              {% for v in api_value.results %}
              NODE_{{ loop.index0 }}={{ v.stdout }}
              {% endfor %}
    

    1. Or run it in the group, e.g.
    - hosts: nodes
      gather_facts: false
      vars:
        whatever_api_result:
          server-1: random-value-a
          server-2: random-value-b
          server-3: random-value-c
      tasks:
        - command: "echo {{ whatever_api_result[inventory_hostname] }}"
          register: api_value
          delegate_to: localhost
        - debug:
            msg: "{{ api_value.stdout }}"
    

    (delegate to localhost for testing)

    gives

    ok: [server-1] => 
      msg: random-value-a
    ok: [server-2] => 
      msg: random-value-b
    ok: [server-3] => 
      msg: random-value-c
    

    Then, in the Jinja template, use hostvars

        - debug:
            msg: |-
              {% for vm in groups.nodes %}
              NODE_{{ loop.index0 }}={{ hostvars[vm].api_value.stdout }}
              {% endfor %}
          run_once: true
    

    gives also what you want

      msg: |-
        NODE_0=random-value-a
        NODE_1=random-value-b
        NODE_2=random-value-c
    

    Optionally, iterate hostvars. This gives the same result

        - debug:
            msg: |-
              {% for k,v in hostvars.items() %}
              NODE_{{ loop.index0 }}={{ v.api_value.stdout }}
              {% endfor %}
          run_once: true