Search code examples
ansiblefortigate

Loop with subelements filter


Variable:

customers:
  - name: CompanyX
    destination_addresses:
      - 192.168.0.0/24
      - 192.168.1.0/24

  - name: CompanyY
    destination_addresses:
      - 192.168.2.0/24
      - 192.168.3.0/24

I'm trying to create address objects for each address in destination_addresses, and create an address group object that stores all addresses per customer.
The creation of each address works as expected like this:

- name: add address object for destination networks
  fortinet.fortios.fortios_firewall_address:
    state: present
    firewall_address:
      name: "{{ item.0.name }}-{{ item.1 }}"
      subnet: "{{ item.1 }}"
  loop: "{{ customers | subelements('destination_addresses') }}"

This creates:

  • CompanyX-192.168.0.0/24
  • CompanyX-192.168.1.0/24
  • CompanyY-192.168.2.0/24
  • CompanyY-192.168.3.0/24

But I'm struggling how to group the address objects.
This is what I use now:

- set_fact:
    grp_members: "{{ grp_members | default([]) + [{ 'name': item.0.name ~ '-' ~ item.1 }] }}"
  loop: "{{ customers | subelements('destination_addresses') }}"
  loop_control:
    extended: yes

- name: create address group
  fortinet.fortios.fortios_firewall_addrgrp:
    state: present
    firewall_addrgrp:
      name: "{{ item.name }}"
      member: "{{ grp_members }}"
  loop: "{{ customers }}"

Which creates the group CompanyX and CompanyY but with all addresses in each group, because the grp_members variable contains all addresses.
How can I limit the group members to only contain the addresses for CompanyX & CompanyY separately?

Current output:

- debug:
    var: grp_members

    "grp_members": [
        {
            "name": "CompanyX-192.168.0.0/24"
        },
        {
            "name": "CompanyX-192.168.1.0/24"
        },
        {
            "name": "CompanyY-192.168.2.0/24"
        },
        {
            "name": "CompanyY-192.168.3.0/24"
        }
    ]

Desired result for each customer:

    "grp_members": [
        {
            "name": "CompanyX-192.168.0.0/24"
        },
        {
            "name": "CompanyX-192.168.1.0/24"
        }
    ]
    "grp_members": [
        {
            "name": "CompanyY-192.168.2.0/24"
        },
        {
            "name": "CompanyY-192.168.3.0/24"
        }
    ]

The fortinet.fortios.fortios_firewall_addrgrp module expects a dictionary in the above syntax.


Solution

  • With the changed conditions you need the following:

    - name: create address group
      fortinet.fortios.fortios_firewall_addrgrp:
        state: present
        firewall_addrgrp:
          name: "{{ item.name }}"
          member: "{{ grp_members }}"
      vars:
        grp_members: "{{ [item.name] | product(item.destination_addresses) | map('join', '-') | map('community.general.dict_kv', 'name') }}"
      loop: "{{ customers }}"
    

    You continue iterating over customers. The variable grp_members is generated locally for each iteration.

    1. via product a cross product of the customer with each IP is created
    2. via join the two elements customer name and IP are connected.
    3. dict_kv creates a dict from the list with the key name.

    You don't need your task with set_fact anymore.


    Here you can see the sample output of the joined addresses.

    This task

    - name: generate kv dict with customer-address
      debug:
        msg: "{{ grp_members }}"
      vars:
        grp_members: "{{ [item.name] | product(item.destination_addresses) | map('join', '-') | map('community.general.dict_kv', 'name') }}"
      loop: "{{ customers }}"
    

    produces this output

    TASK [generate kv dict with customer-address] *******************************************************************************
    ok: [localhost] => (item={'name': 'CompanyX', 'destination_addresses': ['192.168.0.0/24', '192.168.1.0/24']}) => {
        "msg": [
            {
                "name": "CompanyX-192.168.0.0/24"
            },
            {
                "name": "CompanyX-192.168.1.0/24"
            }
        ]
    }
    ok: [localhost] => (item={'name': 'CompanyY', 'destination_addresses': ['192.168.2.0/24', '192.168.3.0/24']}) => {
        "msg": [
            {
                "name": "CompanyY-192.168.2.0/24"
            },
            {
                "name": "CompanyY-192.168.3.0/24"
            }
        ]
    }
    

    old post

    I think that's what you're looking for.

    - name: create address group
      fortinet.fortios.fortios_firewall_addrgrp:
        state: present
        firewall_addrgrp:
          name: "{{ item.name }}"
          member: "{{ item.destination_addresses | join(',') }}"
      loop: "{{ customers }}"
    

    I guess that member should be all addresses of the respective customer? These are in a list for the respective customer and you can join them to a string via join function.

    So the task set_fact for grp_members would not be necessary.

    If this is not the result you need, you have to describe it exactly.


    Here you can see the sample output of the joined addresses.

    This task

    - name: Iterate over customers.
      debug:
        msg: "{{ item.name }}: {{ item.destination_addresses | join(',') }}"
      loop: "{{ customers }}"
    

    produces this output

    TASK [Iterate over customers.] ***************************************************************************************
    ok: [localhost] => (item={'name': 'CompanyX', 'destination_addresses': ['192.168.0.0/24', '192.168.1.0/24']}) => {
        "msg": "CompanyX: 192.168.0.0/24,192.168.1.0/24"
    }
    ok: [localhost] => (item={'name': 'CompanyY', 'destination_addresses': ['192.168.2.0/24', '192.168.3.0/24']}) => {
        "msg": "CompanyY: 192.168.2.0/24,192.168.3.0/24"
    }