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!!!
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"]