loopsansiblefirewalld

Iterating over complex lists in Ansible


I have this list of firwalld zones to configure:

fw_zones:
  - name: "example"
    target: "DROP"
    allowed_services:
      - ssh
    icmp_block_inversion: true
    icmp_blocks:
      - fragmentation-needed
  - name: "another_example"
    target: "DROP"
    allowed_services:
      - ssh
      - http
      - ntp
    icmp_block_inversion: true
    icmp_blocks:
      - fragmentation-needed
      - echo-request

and I want to use the firewalld module to do so. While setting new zones shouldn't be a problem, and setting their default target can probably only be done using command module, I am struggling to find a solution for enabling services/icmp blocks since I'm already iterating over a list (fw_zones). Is there a straightforward solution to achieving this?

I tried different setups of this list, like:

fw_zones:
  example:
    target: "DROP"
    allowed_services:
      - ssh
    icmp_block_inversion: true
    icmp_blocks:
      - fragmentation-needed
  another_example:
    target: "DROP"
    allowed_services:
      - ssh
      - http
      - ntp
    icmp_block_inversion: true
    icmp_blocks:
      - fragmentation-needed
      - echo-request

and using | dict2items, but in all cases I couldn't figure out how to iterate over the icmp_blocks and allowed_services lists.


Solution

  • What you're looking for is nested loops.

    While the recommendation is to construct the data in a way that avoids nested loops, it's obvious that sometimes it will make the maintenance of the data complicated, like in your case.

    So, you can either run the loop over some product of your list, or use the nested include_tasks. In my opinion, the difference would be that using product could be more elegant, but nested tasks are more straightforward.

    So, given your initial list, you can do something like the following:

    # playbook.yaml
    ---
    - name: Manage the firewalld settings
      hosts: all
      tasks:
        - name: Looping over the zones
          include_tasks: firewalld_zones_loop.yaml
          loop: "{{ fw_zones }}"
          loop_control:
            loop_var: fw_zone
    
    # firewalld_zones_loop.yaml
    ---
    - name: Looping over the services
      include_tasks: firewalld_services_loop.yaml
      loop: "{{ fw_zone.allowed_services }}"
      loop_control:
        loop_var: fw_service
    
    # firewalld_services_loop.yaml
    ---
    - name: Set the firewall rule
      firewalld:
        zone: "{{ fw_zone.name }}"
        target: "{{ fw_zone.target }}"
        service: "{{ fw_service }}"
        icmp_block: "{{ fw_icmp_block }}"
        state: enabled
      # ...
      loop_control:
        loop_var: fw_icmp_block