Ansible - Iterate two lists but using index of first list

Viewed 57

I am trying to loop over two lists by using index of first list. Here is the code I wrote using python (I will try in python first before implementing in Ansible)

a = ['host1', 'host2']
b = [[1,2,3],[4,5,6,7]]

for idx, i in enumerate(a):
    for j in b[idx]:
        print(i,j)

Here is the output

host1 1
host1 2
host1 3
host2 4
host2 5
host2 6
host2 7

I tried to apply the same thing in Ansible but I could not figure it out. I have resolved it by zipping both and convert them to dictionary and by using dictvar|dict2items|subelements('value') but I am wondering if I can implement it the same way I solved it in python.

Here is my current implementation in Ansible:

- name: KeyValue
  hosts: all
  serial: 1


  tasks:

    - name: Print all 
      set_fact:
        hostnames: ['host1', 'host2', 'host3']
        ports: [[1,2,3,4],[5,6,7], [8,9,10]]
    - name: print vars
      debug:
        msg: "{{ hostnames }} and {{ ports }}"
    - name: joining host and port as dict
      set_fact:
        hostandport: "{{ dict(hostnames | zip(ports)) }}"
    - name: Print the key and value
      debug:
        msg: "key is {{ item.0.key }} and value is {{ item.1 }}"
      loop: "{{ hostandport | dict2items | subelements('value')}}"
      
3 Answers

We can use a set_fact task to transform your two separate lists into a list of dictionaries that looks like this:

[
  {
    "host": "host1",
    "ports": [1,2,3]
  },
  {
    "host": "host2",
    "ports": [4,5,6,7]
  },
  {
    "host": "host3",
    "ports": [8.9.10]
  }
}

This structure is much more amendable to processing with the subelements filter. Here's a runnable example:

- hosts: localhost
  gather_facts: false
  vars:
    hostnames:
      - host1
      - host2
      - host3
    ports:
      - [1,2,3]
      - [4,5,6,7]
      - [8,9,10]

  tasks:
    - name: create host_port dict
      set_fact:
        host_port: >-
          {{ host_port + [{"host": item[0], "ports": item[1]}] }}
      vars:
        host_port: []
      loop: "{{ hostnames|zip(ports) }}"

    - debug:
        msg: "host {{ item.0.host }} port {{ item.1 }}"
      loop: "{{ host_port|subelements('ports') }}"

Which outputs:

TASK [debug] ********************************************************************************************
ok: [localhost] => (item=[{'host': 'host1', 'ports': [1, 2, 3]}, 1]) => {
    "msg": "host host1 port 1"
}
ok: [localhost] => (item=[{'host': 'host1', 'ports': [1, 2, 3]}, 2]) => {
    "msg": "host host1 port 2"
}
ok: [localhost] => (item=[{'host': 'host1', 'ports': [1, 2, 3]}, 3]) => {
    "msg": "host host1 port 3"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 4]) => {
    "msg": "host host2 port 4"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 5]) => {
    "msg": "host host2 port 5"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 6]) => {
    "msg": "host host2 port 6"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 7]) => {
    "msg": "host host2 port 7"
}
ok: [localhost] => (item=[{'host': 'host3', 'ports': [8, 9, 10]}, 8]) => {
    "msg": "host host3 port 8"
}
ok: [localhost] => (item=[{'host': 'host3', 'ports': [8, 9, 10]}, 9]) => {
    "msg": "host host3 port 9"
}
ok: [localhost] => (item=[{'host': 'host3', 'ports': [8, 9, 10]}, 10]) => {
    "msg": "host host3 port 10"
}

Jinja does what you want. For example,

hostandport: |
  {% for host,ports in a|zip(b) %}
  {% for port in ports %}
  {{ host }} {{ port }}
  {% endfor %}
  {% endfor %}

gives

hostandport: |-
  host1 1
  host1 2
  host1 3
  host2 4
  host2 5
  host2 6
  host2 7

Notes:

  • Example of a complete playbook for testing
- hosts: localhost
  vars:
    a: ['host1', 'host2']
    b: [[1,2,3],[4,5,6,7]]
    hostandport: |
      {% for host,ports in a|zip(b) %}
      {% for port in ports %}
      {{ host }} {{ port }}
      {% endfor %}
      {% endfor %}
  tasks:
    - debug:
        var: hostandport

  • Split the lines and items if you want to iterate the hosts and ports. For example,
    - debug:
        msg: "Wait for port: {{ item.1 }} at host: {{ item.0 }}"
      loop: "{{ hostandport.splitlines()|map('split')|list }}"

gives (abridged)

  msg: 'Wait for port: 1 at host: host1'
  msg: 'Wait for port: 2 at host: host1'
  msg: 'Wait for port: 3 at host: host1'
  msg: 'Wait for port: 4 at host: host2'
  msg: 'Wait for port: 5 at host: host2'
  msg: 'Wait for port: 6 at host: host2'
  msg: 'Wait for port: 7 at host: host2'

  • Declare the list in vars if you want to use it later. For example, the playbook below gives the same result
- hosts: localhost
  vars:
    a: ['host1', 'host2']
    b: [[1,2,3],[4,5,6,7]]
    hostandport: |
      {% for host,ports in a|zip(b) %}
      {% for port in ports %}
      {{ host }} {{ port }}
      {% endfor %}
      {% endfor %}
    hostandport_list: "{{ hostandport.splitlines()|map('split')|list }}"
  tasks:
    - debug:
        msg: "Wait for port: {{ item.1 }} at host: {{ item.0 }}"
      loop: "{{ hostandport_list }}"

  • The most straightforward way is to create a list from the beginning if you want to iterate it. For example, the declarations below
hostandport: |
  {% for host,ports in a|zip(b) %}
  {% for port in ports %}
  - [{{ host }}, {{ port }}]
  {% endfor %}
  {% endfor %}
hostandport_list: "{{ hostandport|from_yaml }}"

give

hostandport_list:
  - [host1, 1]
  - [host1, 2]
  - [host1, 3]
  - [host2, 4]
  - [host2, 5]
  - [host2, 6]
  - [host2, 7]
  1. zip the two list to obtain a list of host/ports tuples
$ ansible localhost -m debug \
  -e '{"hostnames":["host1","host2"],"ports":[[1,2,3],[4,5]]}' \
  -a msg="{{ hostnames | zip(ports) }}"

gives

    "msg": [
        [
            "host1",
            [
                1,
                2,
                3
            ]
        ],
        [
            "host2",
            [
                4,
                5
            ]
        ]
    ]
  1. create a dict from that list of tuples: keyname is the host, value is the list of ports:
$ ansible localhost -m debug \
  -e '{"hostnames":["host1","host2"],"ports":[[1,2,3],[4,5]]}' \
  -a msg="{{ dict(hostnames | zip(ports)) }}"

gives

    "msg": {
        "host1": [
            1,
            2,
            3
        ],
        "host2": [
            4,
            5
        ]
    }
  1. tranform that dict to a list of {host: x, ports: [y,z]} elements
$ ansible localhost -m debug \
  -e '{"hostnames":["host1","host2"],"ports":[[1,2,3],[4,5]]}' \
  -a msg="{{ dict(hostnames | zip(ports)) | dict2items(key_name='host', value_name='ports') }}"

gives

    "msg": [
        {
            "host": "host1",
            "ports": [
                1,
                2,
                3
            ]
        },
        {
            "host": "host2",
            "ports": [
                4,
                5
            ]
        }
    ]
  1. loop over that list with a subelement loop on ports. Here is a full playbook to test the entire scenario:
---
- hosts: localhost
  gather_facts: false

  vars:
    hostnames:
      - host1
      - host2
      - host3
    ports:
      - [1, 2, 3]
      - [4, 5, 6, 7]
      - [8, 9, 10]

    hosts_list: "{{ dict(hostnames | zip(ports)) | dict2items(key_name='host', value_name='ports') }}"

  tasks:
    - name: loop over our host list for every declared port
      debug:
        msg: "Host {{ item.0.host }} port {{ item.1 }}"
      loop: "{{ hosts_list | subelements('ports') }}"

which gives:

PLAY [localhost] ****************************************************************************************************************************************************************************

TASK [loop over our host list for every declared port] **************************************************************************************************************************************
ok: [localhost] => (item=[{'host': 'host1', 'ports': [1, 2, 3]}, 1]) => {
    "msg": "Host host1 port 1"
}
ok: [localhost] => (item=[{'host': 'host1', 'ports': [1, 2, 3]}, 2]) => {
    "msg": "Host host1 port 2"
}
ok: [localhost] => (item=[{'host': 'host1', 'ports': [1, 2, 3]}, 3]) => {
    "msg": "Host host1 port 3"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 4]) => {
    "msg": "Host host2 port 4"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 5]) => {
    "msg": "Host host2 port 5"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 6]) => {
    "msg": "Host host2 port 6"
}
ok: [localhost] => (item=[{'host': 'host2', 'ports': [4, 5, 6, 7]}, 7]) => {
    "msg": "Host host2 port 7"
}
ok: [localhost] => (item=[{'host': 'host3', 'ports': [8, 9, 10]}, 8]) => {
    "msg": "Host host3 port 8"
}
ok: [localhost] => (item=[{'host': 'host3', 'ports': [8, 9, 10]}, 9]) => {
    "msg": "Host host3 port 9"
}
ok: [localhost] => (item=[{'host': 'host3', 'ports': [8, 9, 10]}, 10]) => {
    "msg": "Host host3 port 10"
}

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Related