Test your knowledge on this topic in the ENAUTO Exam Trainer — 186 questions across 5 interactive modes.
Ansible for Controller-Based Network Automation – ENAUTO v2.0
Exam Relevance
Topic 3.0 Controller-Based Network Automation (30%) is the highest-weighted section.
Exam topic 3.4 specifically tests your ability to construct automation solutions with Ansible that interact with Cisco controllers (Catalyst Center, Meraki, ISE, SD-WAN).
This guide maps every playbook example to the corresponding exam objective.
Exam Topics Covered:
3.4 – Construct a controller-based network automation solution with Ansible
3.1 – Day-0 provisioning with controller-based solutions
3.2 – Python to manage and monitor configurations (comparison)
3.5 – Security automation (ISE with Ansible)
3.6 – Troubleshoot REST API automation solutions
Ansible is an agentless automation tool that uses YAML playbooks to define desired state. For network automation against controllers, Ansible communicates over HTTPS using the httpapi connection plugin — no SSH to individual devices required.
Every Ansible playbook follows the same structure:
---- name: Descriptive name for the play hosts: catalyst_center # Target group from inventory gather_facts: false # Always false for network controllers tasks: - name: Descriptive name for the task cisco.dnac.network_device_info: # Module (collection.module) dnac_host: "{{ ansible_host }}" dnac_port: "{{ dnac_port }}" dnac_username: "{{ ansible_user }}" dnac_password: "{{ ansible_password }}" dnac_version: "{{ dnac_version }}" dnac_verify: false register: result # Capture output - name: Display results ansible.builtin.debug: var: result.dnac_response
Key Playbook Elements
hosts: matches a group or host in inventory
gather_facts: false: always set this for API-based controllers (no SSH facts to gather)
register: saves module output into a variable for later use
debug: prints registered variables for troubleshooting
Variables: host_vars, group_vars, extra-vars
Ansible resolves variables in this precedence order (highest wins):
extra-vars (-e) → highest priority
task vars
play vars
host_vars/
group_vars/
inventory vars
role defaults → lowest priority
Directory structure for variable files:
project/
├── ansible.cfg
├── inventory.yml
├── group_vars/
│ ├── catalyst_center.yml # Variables for all DNAC hosts
│ └── meraki.yml # Variables for all Meraki hosts
├── host_vars/
│ └── dnac1.yml # Variables for specific host
└── playbooks/
└── get_devices.yml
Ansible Vault for Credentials
Never store passwords in plaintext. Use Ansible Vault:
# Create an encrypted variable fileansible-vault create group_vars/catalyst_center/vault.yml# Contents of the vault filevault_dnac_password: Cisco123!vault_dnac_username: admin# Run playbook with vaultansible-playbook playbooks/get_devices.yml --ask-vault-pass# Or use a password fileansible-playbook playbooks/get_devices.yml --vault-password-file ~/.vault_pass
Set host_key_checking = false in lab environments. In production, use proper certificate validation. The exam often tests whether you know this setting exists and when to use it.
Connection Types
Connection Type
Use Case
Protocol
ansible.netcommon.httpapi
Controller APIs (DNAC, Meraki, SD-WAN)
HTTPS
ansible.netcommon.network_cli
Direct device CLI (IOS, NX-OS)
SSH
local
Legacy; runs on control node
Varies
For Exam Topic 3.4
Controller-based automation always uses httpapi (or sometimes local for older collections). Never use network_cli for controller APIs — that is for direct device management (Exam Topic 2.0).
2 – Ansible Collections for Cisco Controllers
Cisco provides dedicated Ansible collections for each controller platform. Each collection abstracts the REST API into declarative Ansible modules.
ansible_network_osmust be cisco.dnac.dnac — not ios, not dnac alone
ansible_connectionmust be ansible.netcommon.httpapi — not network_cli
The httpapi plugin handles token auth automatically (POST to /dna/system/api/v1/auth/token)
a) Get All Network Devices
Ansible Playbook:
---- name: Get all network devices from Catalyst Center hosts: catalyst_center gather_facts: false tasks: - name: Get device list cisco.dnac.network_device_info: dnac_host: "{{ ansible_host }}" dnac_port: "{{ dnac_port }}" dnac_username: "{{ ansible_user }}" dnac_password: "{{ ansible_password }}" dnac_version: "{{ dnac_version }}" dnac_verify: "{{ dnac_verify }}" register: device_list - name: Print device hostnames and IPs ansible.builtin.debug: msg: "{{ item.hostname }} — {{ item.managementIpAddress }}" loop: "{{ device_list.dnac_response.response }}"
Equivalent Python requests:
import requestsfrom requests.auth import HTTPBasicAuthBASE_URL = "https://10.10.20.85"AUTH_URL = f"{BASE_URL}/dna/system/api/v1/auth/token"DEVICES_URL = f"{BASE_URL}/dna/intent/api/v1/network-device"# Step 1: Get token (Ansible httpapi does this automatically)auth_response = requests.post( AUTH_URL, auth=HTTPBasicAuth("admin", "Cisco123!"), verify=False)token = auth_response.json()["Token"]# Step 2: Get devicesheaders = { "X-Auth-Token": token, "Content-Type": "application/json"}response = requests.get(DEVICES_URL, headers=headers, verify=False)devices = response.json()["response"]for device in devices: print(f"{device['hostname']} — {device['managementIpAddress']}")
Ansible Advantage
Notice that the Ansible playbook does NOT need to handle token authentication manually. The cisco.dnac httpapi plugin performs the POST /dna/system/api/v1/auth/token call behind the scenes and injects X-Auth-Token into every subsequent request.
Adding a device returns a taskId, NOT the device itself. You must poll with cisco.dnac.task_info to confirm success. This is one of the most common exam questions for topic 3.4/3.6.
Unlike Catalyst Center, there is no token exchange. The meraki_api_key is sent as the X-Cisco-Meraki-API-Key header on every request. The httpapi plugin handles this automatically.
The response from the SSID module contains the complete, updated SSID configuration. There is no taskId to poll — the operation completes in the API response. This is a key difference from Catalyst Center.
d) Claim Device into Network
---- name: Claim a device into a Meraki network hosts: meraki gather_facts: false tasks: - name: Claim device by serial number cisco.meraki.networks_devices_claim: meraki_api_key: "{{ meraki_api_key }}" networkId: "L_123456789012345678" serials: - "Q2AB-CDE4-FGHI" register: claim_result - name: Show claimed device ansible.builtin.debug: var: claim_result.meraki_response
Meraki Rate Limiting
Meraki enforces a limit of 5 API calls per second per organization. If your playbook has many tasks, you may hit HTTP 429 responses. Use throttle: 1 on tasks or serial: 1 on plays to slow down execution. See 8 – Common Patterns and Gotchas for details.
The cisco.ise collection uses the ERS (External RESTful Services) API. This API is disabled by default on ISE. You must enable it via:
Administration > Settings > API Settings > API Service Settings > ERS (Read/Write)
Forgetting this is a common exam trap.
ISE Auth Method
ISE uses Basic Auth on every request — no token exchange, no session cookie. The cisco.ise modules handle this automatically using ise_username and ise_password.
a) Get All Network Devices
---- name: Get all network devices from ISE hosts: ise gather_facts: false tasks: - name: Get network device list cisco.ise.network_device_info: ise_hostname: "{{ ise_hostname }}" ise_username: "{{ ise_username }}" ise_password: "{{ ise_password }}" ise_verify: "{{ ise_verify }}" register: devices - name: Display devices ansible.builtin.debug: msg: "{{ item.name }} — {{ item.NetworkDeviceIPList }}" loop: "{{ devices.ise_response.SearchResult.resources }}"
b) Create Internal User
---- name: Create an internal user in ISE hosts: ise gather_facts: false tasks: - name: Create user cisco.ise.internal_user: ise_hostname: "{{ ise_hostname }}" ise_username: "{{ ise_username }}" ise_password: "{{ ise_password }}" ise_verify: "{{ ise_verify }}" state: present name: "jsmith" password: "UserP@ss123" email: "[email protected]" firstName: "John" lastName: "Smith" identityGroups: "Employee" enabled: true register: user_result - name: Display result ansible.builtin.debug: var: user_result
c) Create Scalable Group Tag (SGT)
---- name: Create a Scalable Group Tag (SGT) in ISE hosts: ise gather_facts: false tasks: - name: Create SGT cisco.ise.sgt: ise_hostname: "{{ ise_hostname }}" ise_username: "{{ ise_username }}" ise_password: "{{ ise_password }}" ise_verify: "{{ ise_verify }}" state: present name: "IoT_Devices" description: "SGT for IoT devices" value: 100 generationId: "0" register: sgt_result - name: Display SGT ansible.builtin.debug: var: sgt_result
SD-WAN vManage uses session authentication. The httpapi plugin performs a login to obtain a JSESSIONID cookie, then includes it on subsequent requests. A CSRF token is also obtained and sent automatically. You do NOT manage this manually in playbooks.
a) Get Device List
---- name: Get SD-WAN device list hosts: sdwan gather_facts: false tasks: - name: Get all devices from vManage cisco.catalystwan.devices_info: device_category: vedges register: devices - name: Display devices ansible.builtin.debug: msg: "{{ item.host_name }} — {{ item.system_ip }} ({{ item.reachability }})" loop: "{{ devices.devices }}"
b) Attach Device Template
---- name: Attach a device template in SD-WAN hosts: sdwan gather_facts: false tasks: - name: Attach template to device cisco.catalystwan.device_templates: state: attached template_name: "Branch-Router-Template" hostname: "BR-ROUTER-01" device_specific_vars: system_ip: "10.0.0.1" site_id: "100" hostname: "BR-ROUTER-01" register: attach_result - name: Show result ansible.builtin.debug: var: attach_result
Like Catalyst Center, SD-WAN template pushes and policy activations return an action ID. You may need to poll for completion status depending on the module used.
7 – Comparison: requests vs SDK vs Ansible
This table summarizes the three automation approaches tested on the ENAUTO exam. Understanding when to use each is critical for exam topic 3.4.
The exam expects you to recognize which approach (requests, SDK, Ansible) is most appropriate for a given scenario. Key signals:
“orchestrate across multiple controllers” → Ansible
“debug a specific API call” → requests
“build a web application” → SDK
“ensure idempotent configuration” → Ansible
“CI/CD pipeline for network changes” → Ansible
8 – Common Patterns and Gotchas
Collection Version Mismatches
The cisco.dnac collection version must match your Catalyst Center software version. If you use dnac_version: "2.3.7.9" in inventory but have an older collection installed, modules may fail with unexpected errors. Always check:
ansible-galaxy collection list | grep cisco.dnac
httpapi vs local Connection Confusion
Older Ansible examples may use connection: local. Modern Cisco collections require connection: ansible.netcommon.httpapi. Using local may cause authentication failures or unexpected behavior.
Every write operation on Catalyst Center returns a taskId. If you forget to poll the task, your playbook may report “success” even though the actual operation failed on the controller.
- name: Wait for rate limit ansible.builtin.pause: seconds: 1
ISE ERS API Must Be Enabled
The cisco.ise collection communicates with the ERS API, which is disabled by default. If you see Connection refused or 403 Forbidden, the ERS API is likely not enabled. This is a common exam trick — the playbook YAML is correct but the ISE node is not configured to accept ERS requests.
9 – Exam-Style Scenarios
Scenario 1: 401 Unauthorized Against Catalyst Center
An engineer writes an Ansible playbook to retrieve devices from Catalyst Center. The playbook fails with HTTP 401 Unauthorized. The credentials are confirmed correct. The inventory file contains:
The connection type and network OS are wrong. For Catalyst Center controller automation:
ansible_connection must be ansible.netcommon.httpapi (not network_cli)
ansible_network_os must be cisco.dnac.dnac (not ios)
Using network_cli attempts SSH to the controller instead of HTTPS API calls. The httpapi plugin is what handles the automatic token authentication via POST /dna/system/api/v1/auth/token.
Scenario 2: Meraki 429 Too Many Requests
A playbook loops through 50 Meraki networks to update SSID configurations. After processing a few networks, tasks start failing with HTTP 429 errors. How do you fix this?
Answer
Meraki enforces a rate limit of 5 API calls per second per organization. The loop is sending requests too fast.
Fix options:
Add throttle: 1 to the looping task to limit concurrency
An Ansible playbook to create a Scalable Group Tag on ISE fails with a connection error. The ISE web GUI is accessible and the credentials work for GUI login. What is missing?
Answer
The ERS (External RESTful Services) API is not enabled on ISE. By default, ERS is disabled. The cisco.ise collection exclusively uses the ERS API.
To fix: Navigate to Administration > Settings > API Settings > API Service Settings and enable ERS (Read/Write).
Additionally, the user account must have the ERS Admin role assigned, not just a GUI admin role.
Scenario 4: Which Connection Type for cisco.dnac?
On the exam, you see a fill-in-the-blank question:
The cisco.dnac collection uses the httpapi connection plugin to communicate with Catalyst Center over HTTPS. Never use network_cli (that is for SSH-based device management) or local (legacy approach).
Scenario 5: Playbook Works for GET but Fails for POST
An engineer has a working playbook that retrieves devices from Catalyst Center. They add a new task to add a device using cisco.dnac.network_device. The task completes with changed: true, but the device never appears in Catalyst Center. What went wrong?
Answer
Catalyst Center write operations are asynchronous. The network_device module returns a taskId, not confirmation that the device was added. The playbook reported changed: true because the API accepted the request, but the actual provisioning may have failed.
Fix: Add a task polling step using cisco.dnac.task_info:
gather_facts: true — This should be false. Ansible tries to gather facts via SSH/setup module, which does not work against a cloud API controller. Always set gather_facts: false for controller automation.
ansible_host inside the module parameters — ansible_host is an inventory/connection variable, NOT a module parameter. It should not appear inside the task’s module arguments. The cisco.meraki.organizations_info module uses meraki_api_key and connects via the httpapi plugin which reads connection info from inventory variables.
An engineer runs ansible-playbook get_devices.yml and the playbook fails because the vault-encrypted password variable cannot be decrypted. What command-line flag is missing?
Answer
The engineer must provide the vault password at runtime using one of:
Without --ask-vault-pass or --vault-password-file, Ansible cannot decrypt vault_dnac_password and the variable resolves to the encrypted blob, causing authentication failure.
Scenario 8: SD-WAN Template Push Appears Successful but No Change
A playbook attaches a device template via cisco.catalystwan.device_templates. The task shows ok but the device configuration does not change in vManage. What should the engineer check?
Answer
SD-WAN template operations are asynchronous, similar to Catalyst Center. The ok status means vManage accepted the request, not that the push completed. The engineer should:
Check if the module returns an action/task ID and poll for completion
Verify the device-specific variables are correct — an invalid variable causes a silent failure during template push
Check vManage Task View (Monitor > Task View) for the actual push status
Ensure the device is reachable from vManage and in vManage mode (not CLI mode)