Home > database >  Ansible - Iterate two lists but using index of first list
Ansible - Iterate two lists but using index of first list

Time:09-21

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')}}"
      

CodePudding user response:

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"
}

CodePudding user response:

  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

CodePudding user response:

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

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
  • Related