loopsansibleciscofortigate

Ansible looped task executing on non-targeted hosts


I've got an Ansible playbook I'm using to do SNMP user operations on Cisco and FortiGates simultaneously. Each task file we've written works fine on its own. However, when trying to run at a site level with both types of devices, the playbook breaks. Using a loop for the FortiGate user list, the task "bleeds" over and tries to run on the Cisco devices, which obviously fails, despite a conditional preventing that from happening in the first place. This is running on RHEL 9.4, Ansible 9.7, and Python 3.11.7. host_pinned is the strategy.

The Ansible FortiGate code is pretty straightforward. It first does an API call to get all the SNMP user info. Then, it loops through the output and removes any SNMP users not matching the conditional.

MRE LOOP PLAYBOOK:

- name: Configure SNMP users
  hosts: inventory
  gather_facts: false
  ignore_errors: false

  tasks:
    - name: Include Authentication Tasks
      ansible.builtin.import_tasks: GlobalLibrary/import_tasks/default-authentication-tasks.yml
      run_once: true

    - block:    
        - name: Include FortiOS SNMP tasks
          ansible.builtin.import_tasks: import_tasks/FortiOS_snmp_update-task.yml
          when: ansible_network_os == 'fortinet.fortios.fortios'
      no_log: false
      rescue:
        - name: Log failure and rescue
          ansible.builtin.debug:
            msg: "{{ inventory_hostname }} has experienced a failure"
        - ansible.builtin.lineinfile:
            insertafter: EOF
            dest: "outputs/failure_report.txt"
            line:
              "{{ inventory_hostname }} - REASON: {{ ansible_failed_result.msg }}<br>"

LOOP TASK:

- name: Gather SNMP info
  fortinet.fortios.fortios_json_generic:
    vdom: "root"
    json_generic:
        method: "GET"
        path: "/api/v2/cmdb/system.snmp/user"
  register: snmp_output 

- name: Remove non-standard usernames from FortiGate devices
  fortinet.fortios.fortios_system_snmp_user:
    vdom: "root"
    state: "absent"
    system_snmp_user:
      name: "{{ item }}"
  loop: "{{ snmp_output | community.general.json_query('meta.results[*].name') }}"
  when: 
    - item != "test1"
    - item != "test2"

This works fine and executes perfectly. When I run the playbook as MRE without the Cisco task file, it does not try to run on the Cisco devices.

If I add in the full playbook...

FULL LOOP PLAYBOOK:

- name: Configure SNMP users
  hosts: inventory
  gather_facts: false
  ignore_errors: false

  tasks:
    - name: Include Authentication Tasks
      ansible.builtin.import_tasks: GlobalLibrary/import_tasks/default-authentication-tasks.yml
      run_once: true

    - block:  
        - block:
            - name: Include IOS SNMP tasks
              ansible.builtin.import_tasks: import_tasks/IOS_snmp_update-task.yml

            - name: Save config
              cisco.ios.ios_config:
                save_when: always
          when: ansible_network_os == 'cisco.ios.ios'

        - block:
            - name: Include NX-OS SNMP tasks
              ansible.builtin.import_tasks: import_tasks/NX-OS_snmp_update-task.yml

            - name: Save config
              cisco.nxos.nxos_config:
                save_when: always
          when: ansible_network_os == 'cisco.nxos.nxos'

        - block:
            - name: Include ASA SNMP tasks
              ansible.builtin.import_tasks: import_tasks/ASA_snmp_update-task.yml

            - name: Remove non-standard SNMPv3 hosts on {{ inventory_hostname }}
              cisco.asa.asa_config:
                src: "outputs/{{ inventory_hostname }}_host_script.txt"

            - name: Remove non-standard SNMPv3 users on {{ inventory_hostname }}
              cisco.asa.asa_config:
                src: "outputs/{{ inventory_hostname }}_user_script.txt"

            - name: Remove non-standard SNMPv3 groups on {{ inventory_hostname }}
              cisco.asa.asa_config:
                src: "outputs/{{ inventory_hostname }}_group_script.txt"

            - name: Save config
              cisco.asa.asa_config:
                save_when: always
          when: ansible_network_os == 'cisco.asa.asa'
        
        - name: Include FortiOS SNMP tasks
          ansible.builtin.import_tasks: import_tasks/FortiOS_snmp_update-task.yml
          when: ansible_network_os == 'fortinet.fortios.fortios'
          
      no_log: false
      rescue:
        - name: Log failure and rescue
          ansible.builtin.debug:
            msg: "{{ inventory_hostname }} has experienced a failure"
        - ansible.builtin.lineinfile:
            insertafter: EOF
            dest: "outputs/failure_report.txt"
            line:
              "{{ inventory_hostname }} - REASON: {{ ansible_failed_result.msg }}<br>"

... and run it against the whole site (which contains Cisco devices and the FortiGate), I get the error where it's trying to execute the loop on the switches for some reason.

FULL LOOP TARGETING SITE ERROR:

TASK [Remove non-standard usernames from FortiGate devices] ********************
fatal: [switch1]: FAILED! => 
  msg: 'Invalid data passed to ''loop'', it requires a list, got this instead: . Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup.'
...
TASK [Remove non-standard usernames from FortiGate devices] ********************
fatal: [switch2]: FAILED! => 
  msg: 'Invalid data passed to ''loop'', it requires a list, got this instead: . Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup.'
...

etc.

There's no reason that task should be running on any switches and the switch task files don't have loops at all. It's almost like there's no conditional on it at all (which there clearly is).

INVENTORY FILE SAMPLE:

inventory:
  children:
    lab:
#
lab:
  hosts:
    switch1:
      ansible_host: 10.1.2.2
      ansible_network_os: cisco.ios.ios
      ansible_connection: ansible.netcommon.network_cli
#
    firewall1:
      ansible_host: 10.1.2.3
      ansible_network_os: fortinet.fortios.fortios
      ansible_connection: ansible.netcommon.httpapi
      ansible_httpapi_use_ssl: yes
      ansible_httpapi_validate_certs: no
      ansible_httpapi_port: 4443

Let me know if any other info might be needed or is helpful. Thanks in advance!!!


Solution

  • I ended up getting an answer to this from another post I did where I was trying this a different way. I ended up doing the loop in this format:

    - name: Remove non-standard usernames from FortiGate devices
      fortinet.fortios.fortios_system_snmp_user:
        vdom: "root"
        state: "absent"
        system_snmp_user:
          name: "{{ item.name }}"
      loop: "{{ snmp_output.meta.results | default([]) }}"
      when: item.name not in ["test1", "test2"]