Network Automation with Ansible
Two ways to configure IOS: declarative or imperative
Two Ansible modules can add the same VLAN, and the difference between them is the spine of this whole page:
# Imperative: hand the device CLI lines
- cisco.ios.ios_config:
parents: vlan 20
lines: [name SERVERS]
# Declarative: hand the module the desired resource
- cisco.ios.ios_vlans:
config: [{ vlan_id: 20, name: SERVERS }]
state: merged
The ios_config[1] version is imperative: you type CLI lines and the module sends them, exactly as you would at a console. The ios_vlans[2] version is declarative: you describe the end state as structured data and the module makes the device match it. A resource module is that declarative kind, one per feature area (VLANs, ACLs, an OSPF process), and it runs the read-diff-push loop the diagram traces: it reads the running config off the device, computes the difference against your data, and pushes only that delta over a connection plugin (the transport, covered in "Reaching the device").
Two properties fall out of that loop. The module is declarative, so you state the goal rather than the ordered steps, and it is idempotent: applying the same input converges the device once and then reports no further change. Run ios_vlans with state: replaced and the running VLAN database is rewritten to match your list; run it again and it reports changed: false. That zero-change re-run is the signal the device already matches source control, which is why the guidance is one line: reach for the resource module when one models the feature, and keep ios_config for raw CLI that nothing else covers (network resource modules[3]).
Resource modules: the state keyword and module choice
One keyword, state, decides how much of the device a resource module is allowed to change, and getting it wrong is the difference between fixing one ACL and wiping every ACL on the box. Read the four write states as an axis of increasing scope:
merged(the default) adds or updates the attributes you list and removes nothing. Listing VLAN 10 with a new name changes that name and leaves every other VLAN alone.replacedrewrites each resource you name to match your definition exactly, but only the resources you name.state: replacedon one named ACL[4] rewrites that ACL's entries and touches no other ACL.overriddenrewrites the whole class and deletes anything you did not list.state: overriddenonios_vlansreconciles every VLAN on the device to your list and removes the VLANs absent from it.deletedremoves the resources you name (or, with an emptyconfig, the whole class).
The trap the exam leans on is replaced versus overridden. Both rewrite config, so they read as interchangeable, but replaced is per-resource and leaves unlisted resources alone, while overridden is device-wide and deletes them. To remove VLANs no longer in source control the answer is overridden; to fix one VLAN without disturbing the others it is replaced. The diagram draws these as nested boundaries: the inner box is the attributes merged touches, the middle box is the one resource replaced rewrites, and the outer box is the whole class overridden reconciles.
Read-only states never touch the device
Three more states transform config without changing anything. gathered returns the live config as structured facts (the read half of the loop), rendered turns your input into device CLI without connecting at all, and parsed converts a supplied running-config text into structured data for migration. Because rendered and parsed open no connection, they run in CI with no lab.
Choosing the right module
The naming rule is short: the modern declarative modules are plural, and the singular ones are their removed predecessors.
| Module | Manages |
|---|---|
cisco.ios.ios_vlans | The VLAN database (singular ios_vlan was removed) |
cisco.ios.ios_acls | Named/numbered IPv4/IPv6 ACLs as structured ACEs |
cisco.ios.ios_ospfv2[5] | The OSPFv2 process: areas, networks, redistribution, timers |
cisco.ios.ios_ospf_interfaces | Per-interface OSPF settings |
cisco.ios.ios_interfaces[6] | L1/L2 attributes: description, enabled, mtu, speed, duplex |
cisco.ios.ios_l3_interfaces[7] | L3 addressing: IPv4/IPv6 on an interface |
Two naming traps recur. The singular cisco.ios.ios_vlan was deprecated and removed from the cisco.ios collection after 2022-06-01, and even before removal it could only add VLANs, never declaratively replace the set, so the plural ios_vlans is always today's call. And the interface split is real: ios_interfaces owns L1/L2 attributes while ios_l3_interfaces owns IP addressing, so trying to set an address with ios_interfaces puts it nowhere. Routing splits the same way, ios_ospfv2 for the process and ios_ospf_interfaces per interface. A stem that says manage the OSPF process declaratively wants ios_ospfv2, not ios_config; return the current config as structured data with no change wants state: gathered, the one read that hits the live device.
ios_config for raw CLI: parents, match, save_when
ios_config is the right tool for raw CLI that no resource module models, banners, one-off global commands, service tweaks, and it earns three options worth knowing cold. A single task is CLI with a little structure around it:
- cisco.ios.ios_config:
parents: interface GigabitEthernet1/0/1
lines:
- ip access-group ACL_IN in
- description UPLINK-TO-CORE
match: line
save_when: changed
lines (alias commands) is the ordered CLI to send. parents is the ordered configuration-mode context the module enters first, so the two lines land inside interface GigabitEthernet1/0/1 rather than at the global level. That placement is what ios_config compares against show running-config, so the text must match what the device stores, including full non-abbreviated forms: int Gi1/0/1 will not match the stored interface GigabitEthernet1/0/1 and re-sends every run.
match decides whether the task is idempotent
Whether a second run reports ok or changed comes down to match, which defaults to line. The diagram splits on it. With match: line the module reads the running config, compares each candidate line, and sends only the missing ones, so a converged device receives nothing, that is idempotency, and it is on by default; strict and exact are the other diff-aware values (they tighten how ordering and completeness are compared) and behave the same way for this purpose, diffing against the running config and sending only what is missing. match: none alone skips the comparison and re-sends the full lines list on every run, reporting changed forever. This is the single most tested fact about the module: idempotent by default, but match: none throws idempotency away. Reserve it for a genuine unconditional replay, never as a way to "make sure it applies."
save_when persists to startup without churn
An ios_config change lands in running-config and stops there; save_when decides when it is copied to startup so it survives a reload. The default is never.
save_when | Copies running to startup when | Idempotent |
|---|---|---|
never (default) | never | yes, but the change is not persisted |
modified | running and startup differ | yes |
changed | this task changed running-config | yes |
always | every run | no, reports changed each time |
The favourite detail is modified versus changed: modified looks at the device and saves whenever running and startup differ (including an out-of-band change someone else made), while changed looks at the task and saves only when this run altered config. Both keep a converged play green; a stem that mentions an out-of-band change points at modified. save_when: always is the usual reason a playbook never turns green, because it saves and reports changed on every run. When a structured feature (VLANs, OSPFv2, an ACL, an interface address) is in play, the maintainable answer is the matching resource module, not a hand-built ios_config block, and ios_command[8] is a trap that only runs show/exec output and cannot change config at all.
Reaching the device: connection plugins and inventory
A switch runs no on-box Python, so Ansible keeps the module on the control node, runs it locally, and reaches the device through a connection plugin that speaks the device's own protocol. Two inventory variables steer that: ansible_connection picks the transport, and ansible_network_os: cisco.ios.ios names the platform so the plugin loads the right command set and parser (set network_os wrong and you get a wall of parser errors). The three network transports live in the ansible.netcommon collection, and the diagram traces one task from the control node through whichever you pick.
| Aspect | network_cli | netconf | httpapi |
|---|---|---|---|
ansible_connection | ansible.netcommon.network_cli | ansible.netcommon.netconf | ansible.netcommon.httpapi |
| Transport / port | SSH, TCP 22 | NETCONF over SSH, TCP 830 | HTTP(S), TCP 443 with SSL |
| On the wire | CLI commands | NETCONF RPC (XML) | JSON over REST |
| Typical target | IOS/IOS-XE via cisco.ios | YANG config datastores | RESTCONF, Catalyst Center |
network_cli[9] is the SSH command line and the workhorse for ios_config and the resource modules. netconf[10] exchanges XML RPCs on port 830 and needs the ncclient library on the control node, which is itself the tell that a scenario means NETCONF. httpapi[11] carries RESTCONF and controller APIs such as Cisco Catalyst Center[12] as JSON over HTTPS; turn on TLS with ansible_httpapi_use_ssl: true and the port defaults to 443. An API transport reads and writes structured data instead of screen-scraping a terminal.
Log in, then elevate with become: enable
On network_cli, authenticating and reaching config mode are two steps, and forgetting the second is the classic privilege error. ansible_user and ansible_password open the SSH session and land you at user EXEC (switch>); configuration lives behind privileged EXEC (switch#), so you elevate with become: true and become_method: enable (plus ansible_become_password when an enable secret is set). Read this carefully: become here is not sudo. The meaningful become_method on a network device is enable, which maps to the IOS enable command, so an answer using become_method: sudo is wrong by construction. A read-only ios_command running show stays at user EXEC and needs no elevation.
Scope variables with group_vars and host_vars
Credentials belong in inventory, not playbooks. Put settings shared by a class of devices in group_vars/<group>.yml and per-device exceptions in host_vars/<host>.yml; where both set the same variable, host_vars outranks group_vars[13]. So set the norm once at the group and override only the exception per host. Two guardrails: group_vars/host_vars are plain YAML on disk, so any secret in them must be encrypted with Ansible Vault (next section), and a host in many groups inherits from all of them, which makes a value's origin hard to trace.
Protecting secrets with Ansible Vault
A network-automation repo fills with values you cannot commit as clear text: the enable secret, a Catalyst Center API token, a TACACS password. Ansible Vault[14] keeps them in the same Git repo as your playbooks by storing them as ciphertext. The mental model is one sentence: ansible-vault applies symmetric AES256 encryption to content at rest, and the vault password is the only key. At rest is load-bearing, Vault protects files on disk, not the SSH or TLS session to the device, which is secured separately. Debunk the common confusion on sight: the ansible-vault CLI baked into Ansible and the HashiCorp Vault server (a network-reachable secrets manager) share a word and nothing else; an ansible-vault encrypt on a .yml file always means the Ansible tool.
The diagram follows one secret end to end. You run ansible-vault encrypt (or encrypt_string) once, turning plaintext into ciphertext; that ciphertext is what you commit, so the repo never holds the plaintext. At run time you supply the vault password, Ansible decrypts the value in memory for that run, and never writes plaintext back to disk.
Encrypt a whole file, or a single value
ansible-vault encrypt group_vars/all/vault.yml replaces a file's whole contents with one $ANSIBLE_VAULT;1.1;AES256 blob, unreadable in a diff, which suits a dedicated secrets file. You still work with it through ansible-vault edit, view, and rekey (which re-encrypts under a new password in one step, with no plaintext-on-disk window). ansible-vault encrypt_string instead encrypts one value[15] and prints a !vault | YAML snippet you paste into an otherwise plaintext vars file, so variable names and structure stay readable in review while only the secret is opaque.
The password is the whole trust boundary
Because the AES key is derived from the vault password, protecting that password is the entire job. Supply it only at run time: --ask-vault-pass prompts interactively (fine for a human, useless in a pipeline that has no terminal to answer), and --vault-password-file ~/.vault_pass reads it from a file or an executable script, which is how a CI job injects it from its own protected store. When one play mixes secrets under different passwords, --vault-id label@source[16] names each (--vault-id prod@prompt --vault-id lab@lab-pass.txt) and is also the supported bridge to an external secrets manager through a client script.
The headline anti-pattern the exam tests: encrypting a vars file but committing its vault password file next to it gives no real protection, because anyone who clones the repo then holds both halves. The password must reach the run from outside source control, a prompt, a CI secret variable, or a secrets manager, so repository access alone never yields plaintext. And "commit it in a private repo" is not the fix: private is access control, not encryption.
Playbooks, roles, idempotency, and safe change
Run a fully converged playbook a second time and the recap reads changed=0 on every host, that zero is the whole point. Idempotency (introduced in "Two ways to configure IOS") shows up operationally in the PLAY RECAP[17]: a task reports changed only when it actually altered the device, so a green changed=0 run tells you the running config still matches source control, and a surprise changed=2 on a play you did not edit tells you something drifted. The crucial caveat is that idempotency is a property of the module, not of Ansible: a resource module diffs and converges, but a raw command module (ios_command, shell, raw) cannot know its effect was already present and reports changed every run, so a changed=0 recap means nothing for a play built out of those.
Preview safely with check mode and diff
ansible-playbook --check --diff is the dry run before production. Check mode[18] has each module compute the change it would make and report changed/ok without sending anything, and --diff prints the exact configuration delta line by line. Check mode is per-module, the network resource modules support it while a raw command module cannot predict its own effect, and --diff also works on a real apply, turning an ordinary run into an audit log of what moved.
Roles package automation; handlers fire once
A role[19] gives reusable automation a fixed layout, tasks/ handlers/ templates/ vars/ defaults/ meta/, and Ansible auto-loads main.yml from each. The defaults/ versus vars/ split is the part exams probe: defaults/main.yml holds the lowest-precedence variables a caller is meant to override, while vars/main.yml holds high-precedence values that an ordinary playbook variable will not. A handler[20] is a task that runs only when another task notifys it, and only if that task reported changed; all notified handlers run once, at the end of the play, in definition order, exactly the semantics for a single save config after a batch of changes and never on a converged run.
One template for a fleet
Jinja2[21] is how one playbook fits devices that are not identical. Ansible evaluates {{ ... }} from inventory, host_vars, and group_vars; the template module renders a .j2 file per host; loop (the modern replacement for with_items) repeats a task once per list element for a variable-length set of VLANs or ACLs; and when gates a task on an expression such as a device fact, so a single play branches across a heterogeneous fleet. Inventory data plus one template equals per-device config.
Failure handling and rollback
Internalize the rule first: Ansible does not roll back. When a task fails the play stops for that host, but earlier changes stay applied, so rollback is something you write, using the block[22] construct the diagram traces. block groups the change tasks; rescue runs only if a block task fails (put undo/restore steps here); always runs on both paths for cleanup or a final notify. Two task keywords decide whether the rescue path is reached, and they are the pair the exam most often swaps: failed_when[23] redefines what counts as a failure (a command can exit 0 yet be a failure because its output contains Invalid), while ignore_errors: true changes what happens after a failure by letting the play continue. They are not interchangeable, and an answer that uses ignore_errors to mean redefine-failure is wrong.
Reading device facts and operational state
SSH into a switch and run show version: it prints the model, chassis serial, and running image. Open show running-config and none of those three appear. That gap is this section. A managed device holds two kinds of data. Configuration is the intended settings you push, interfaces, routing, ACLs, VLANs, what show running-config returns and NETCONF calls a configuration datastore (RFC 6241[10]). Operational state is the live, read-only data the device computes about itself: model, serial number, software version, oper-status and counters, neighbor and route tables, uptime. You cannot set operational state; the device reports it.
Device facts and asset inventory are operational state, so a task that says "build a CMDB keyed on serial number" is asking for state, never configuration. The diagram fixes that split, and every tool below exposes a config-read beside a state-read; every trap in this area is a config-read offered where the goal needs the state-read.
The three surfaces, one split
- Ansible.
cisco.ios.ios_facts[24] gathers a device intoansible_net_*variables (ansible_net_model,ansible_net_serialnum,ansible_net_version,ansible_net_hostname), the standard asset feed;gather_subset(all,min,hardware,interfaces,config) tunes cost, andhardwareis usually right for inventory. The trap: a resource module'sstate: gatheredreturns that one feature's structured configuration, not a serial or model, so when the stem asks for hardware/software inventory,ios_factsis the answer andgatheredis the distractor. - RESTCONF turns the split into a query knob:
GETwithcontent=nonconfigreturns operational state only,content=configreturns configuration only, and omittingcontentreturns both (the default isall) (RFC 8040 §4.8.1[11]). So the common misread, that a plain RESTCONFGETreturns just configuration, is wrong; read state alone withcontent=nonconfig. - NETCONF makes it two operations:
get-configreturns configuration only from a named datastore, whilegetreturns configuration and operational state together. To read counters or neighbor state over NETCONF you issueget, neverget-config.
RESTCONF content=config lines up with NETCONF get-config, and RESTCONF's default all with NETCONF get; the one asymmetry is that NETCONF has no single state-only verb.
Python: retrieve, then parse
In Python you read live state with a purpose-built call, then parse the reply, never regex raw text. ncclient[25] issues the same RPCs, m.get(...) for config-plus-state and m.get_config(source='running', ...) for config only, returning XML you parse with xmltodict or lxml. netmiko runs send_command("show version") and returns raw text whose column positions shift between releases, so scraping it with regular expressions is brittle. Turn the reply into a dictionary instead: Cisco pyATS/Genie[26] parses hundreds of show commands (device.parse("show version")["version"]["chassis_sn"]), and netmiko can apply a TextFSM template with use_textfsm=True. Either way you key on named fields, so a cosmetic output change does not break the collector. One rule collapses the whole section: choose the read whose result set includes operational state, and treat every config-only read (gathered, content=config, get-config) as the trap whenever the question asks for device facts or asset inventory.
Ansible resource-module state values
| Behavior | merged | replaced | overridden | deleted |
|---|---|---|---|---|
| Default state | Yes | No | No | No |
| Pushes config to device | Yes | Yes | Yes | Yes |
| Scope of change | Listed attributes | Listed resources | Whole class | Listed resources |
| Deletes unlisted resources | No | No | Yes | No |
| Typical use | Add or adjust | Enforce one resource | Golden config | Decommission |
Decision tree
Sharp facts the exam loves — give these one last read before exam day.
Cheat sheet
Sharp facts the exam loves — scan these before test day.
- cisco.ios.ios_vlans with state replaced converges the VLAN database
The
cisco.ios.ios_vlansresource module is the modern declarative module for VLANs; withstate: replacedit overwrites the config of the listed VLANs so the running VLAN entries match the playbook exactly, giving idempotent convergence rather than blind CLI pushes.Trap Distractor uses the deprecated singular
cisco.ios.ios_vlanwhich only adds VLANs and cannot declaratively replace the set.4 questions test this
- VLAN 300 on a Cisco IOS-XE switch currently has the name LEGACY and is configured as a remote-SPAN VLAN. An engineer's playbook lists VLAN 300 with only the name RSPAN_NEW and no other attributes, and
- An engineer runs a cisco.ios.ios_vlans task that lists VLANs 500 and 600 but does not specify a state parameter. After the play, the engineer is surprised to find that VLANs 100, 200, and 300 — config
- A brownfield Cisco IOS-XE switch has VLAN 100 named PROD plus dozens of other VLANs. An engineer's cisco.ios.ios_vlans playbook lists VLAN 100 with only its vlan_id, plus two new VLANs 710 and 720 wit
- An engineer must ensure that a set of Cisco IOS-XE access switches carry only VLANs 10, 20, and 30 in their VLAN database, and that any other VLAN currently present is removed, all in one idempotent d
- Resource-module states: merged, replaced, overridden, deleted
Ansible network resource modules accept
statevaluesmerged(default, additive),replaced(replace only the specified resources),overridden(replace and remove every resource not listed), anddeleted. For VLANs,overriddendeletes all VLANs absent from the playbook whilereplacedtouches only the listed ones.Trap Confusing
replaced(per-resource) withoverridden(whole-list) —overriddenwill wipe VLANs you did not list.7 questions test this
- VLAN 300 on a Cisco IOS-XE switch currently has the name LEGACY and is configured as a remote-SPAN VLAN. An engineer's playbook lists VLAN 300 with only the name RSPAN_NEW and no other attributes, and
- An engineer runs a cisco.ios.ios_vlans task that lists VLANs 500 and 600 but does not specify a state parameter. After the play, the engineer is surprised to find that VLANs 100, 200, and 300 — config
- A brownfield Cisco IOS-XE switch has VLAN 100 named PROD plus dozens of other VLANs. An engineer's cisco.ios.ios_vlans playbook lists VLAN 100 with only its vlan_id, plus two new VLANs 710 and 720 wit
- An engineer must build a NetBox source-of-truth import by capturing the current VLAN database from 40 Cisco IOS-XE switches as structured data. The task must run inside a read-only audit window: it mu
- An engineer must ensure that a set of Cisco IOS-XE access switches carry only VLANs 10, 20, and 30 in their VLAN database, and that any other VLAN currently present is removed, all in one idempotent d
- A named IPv4 ACL, EDGE_IN, already contains 12 ACEs on a Cisco IOS-XE device. An engineer must add two new permit ACEs to EDGE_IN using cisco.ios.ios_acls, ensuring the 12 existing ACEs remain in plac
- An engineer must rewrite a single named IPv4 ACL, ACL_MGMT, so it contains exactly the ACEs defined in the playbook, while every other ACL configured on the Cisco IOS-XE device remains completely unto
- gathered, rendered, and parsed states never touch the device
The
gatheredstate returns current config as structured facts,renderedproduces device CLI from the module input without connecting, andparsedconverts a supplied running-config text into structured data. None of these three push configuration to the device.3 questions test this
- In a GitLab CE prevalidation job with no connectivity to production, an engineer wants a cisco.ios.ios_interfaces task to emit the exact IOS-XE CLI it would generate from its structured input (descrip
- An engineer must build a NetBox source-of-truth import by capturing the current VLAN database from 40 Cisco IOS-XE switches as structured data. The task must run inside a read-only audit window: it mu
- During a GitLab CE prevalidation stage that has no reachability to any production Cisco IOS-XE router, an engineer wants to preview the exact IOS-XE OSPF CLI that a cisco.ios.ios_ospfv2 task would gen
- The singular ios_vlan module was deprecated and removed
The legacy
cisco.ios.ios_vlan(singular) module was deprecated and removed from the cisco.ios collection after 2022-06-01; playbooks should use the pluralcisco.ios.ios_vlansresource module for declarative, idempotent VLAN management.- cisco.ios.ios_ospfv2 manages OSPF process config declaratively
cisco.ios.ios_ospfv2 manages the OSPFv2 process (areas, networks, redistribution, timers) as structured data, while cisco.ios.ios_ospf_interfaces manages per-interface OSPF settings; state: merged adds, replaced rewrites one process, overridden rewrites all.
Trap Using the generic ios_config module for OSPF instead of the declarative ios_ospfv2 resource module.
4 questions test this
- An engineer must declaratively manage OSPFv2 on a Cisco IOS-XE router. Process-level requirements include the router-id, redistribution of connected routes, and setting interfaces passive by default.
- An engineer must declaratively manage an OSPFv2 deployment on Cisco IOS-XE: the process areas, networks, and redistribution, plus per-interface hello and dead timers and interface cost. Which module s
- During a GitLab CE prevalidation stage that has no reachability to any production Cisco IOS-XE router, an engineer wants to preview the exact IOS-XE OSPF CLI that a cisco.ios.ios_ospfv2 task would gen
- An OSPFv2 process is already fully defined on a Cisco IOS-XE router. An engineer now needs to declaratively set, per interface, the OSPF network type to point-to-point, the interface cost, and the hel
- cisco.ios.ios_acls manages ACLs as structured ACEs
cisco.ios.ios_acls manages named and numbered IPv4/IPv6 ACLs as a structured list of ACEs; state: replaced rewrites a single named ACL to the given ACEs, while state: overridden reconciles ALL ACLs on the device to the module's definition.
Trap Assuming state: replaced touches every ACL on the device (it is scoped to the named ACL; overridden is the device-wide one).
3 questions test this
- A named IPv4 ACL, EDGE_IN, already contains 12 ACEs on a Cisco IOS-XE device. An engineer must add two new permit ACEs to EDGE_IN using cisco.ios.ios_acls, ensuring the 12 existing ACEs remain in plac
- An engineer must rewrite a single named IPv4 ACL, ACL_MGMT, so it contains exactly the ACEs defined in the playbook, while every other ACL configured on the Cisco IOS-XE device remains completely unto
- An engineer must declaratively manage an IPv6 access list named BLOCK_V6 on several Cisco IOS-XE routers, expressing each entry as structured data with sequence numbers, source, and destination so the
- ios_interfaces vs ios_l3_interfaces split interface concerns
cisco.ios.ios_interfaces manages L1/L2 interface attributes (description, enabled, mtu, speed, duplex); cisco.ios.ios_l3_interfaces manages the L3 addressing (IPv4/IPv6) on those interfaces — the correct module depends on which layer of interface setting is being automated.
Trap Trying to set an IP address with ios_interfaces (addressing lives in ios_l3_interfaces).
4 questions test this
- An engineer must declaratively manage OSPFv2 on a Cisco IOS-XE router. Process-level requirements include the router-id, redistribution of connected routes, and setting interfaces passive by default.
- In a GitLab CE prevalidation job with no connectivity to production, an engineer wants a cisco.ios.ios_interfaces task to emit the exact IOS-XE CLI it would generate from its structured input (descrip
- On several Cisco IOS-XE switches, an engineer must declaratively set only the interface description, admin state (enabled), and MTU. No IP addressing changes are required in this play. The engineer wa
- An OSPFv2 process is already fully defined on a Cisco IOS-XE router. An engineer now needs to declaratively set, per interface, the OSPF network type to point-to-point, the interface cost, and the hel
- ios_config parents builds a hierarchical config block
cisco.ios.ios_configpushes raw CLI imperatively; theparentslist places thelines/commandsinto a config hierarchy, e.g.parents: interface GigabitEthernet1/0/1withlines: ip access-group ACL_IN inenters that interface context before applying the lines.5 questions test this
- An engineer must push an inbound ACL to GigabitEthernet1/0/1 across a group of Cisco IOS-XE switches using cisco.ios.ios_config. The candidate line 'ip access-group MGMT in' must be applied under the
- An engineer must add 'redistribute connected' inside the IPv4 address-family of VRF CUSTA on several IOS-XE routers with cisco.ios.ios_config, where the device config sequence is 'vrf definition CUSTA
- A network team must ensure that interface administrative state, descriptions, and MTU on Cisco IOS-XE switches converge to exactly the values defined in their source of truth, with any manual out-of-b
- An engineer must use cisco.ios.ios_config to add 'neighbor 10.1.1.1 activate' inside the IPv4 address-family of 'router bgp 65001' on several IOS-XE routers, placing the command two levels deep in the
- An engineer must configure identical VTY line settings ('transport input ssh', 'exec-timeout 10 0', and 'logging synchronous') on a group of Cisco IOS-XE switches with cisco.ios.ios_config. The three
- ios_config match none disables idempotent diffing
ios_configdefaults tomatch: line, comparing each candidate line against the running-config and sending only missing lines; settingmatch: nonedisables that comparison so every command is pushed on every run, destroying idempotency.Trap Assuming ios_config is always idempotent — with
match: none(or no diff-aware model) it re-pushes lines every run.5 questions test this
- A cisco.ios.ios_config task pushes a list of NTP and logging commands that already exist verbatim in the running-config, yet the play reports 'changed' on every execution and re-sends all commands, br
- An engineer must push an inbound ACL to GigabitEthernet1/0/1 across a group of Cisco IOS-XE switches using cisco.ios.ios_config. The candidate line 'ip access-group MGMT in' must be applied under the
- A playbook uses cisco.ios.ios_config to push a banner and several service commands that already exist verbatim in the running-config, yet the team wants every command re-sent to the device on each run
- A cisco.ios.ios_config task lists five service and logging commands, but the play reports changed while pushing only two of them; the other three already exist verbatim in the running-config. The engi
- An engineer must configure identical VTY line settings ('transport input ssh', 'exec-timeout 10 0', and 'logging synchronous') on a group of Cisco IOS-XE switches with cisco.ios.ios_config. The three
- save_when: modified writes startup-config only on change
ios_config'ssave_when: modifiedcopies running-config to startup-config only when the task actually changed the running-config, preserving idempotency;save_when: alwayswrites every run and reports changed each time.5 questions test this
- A cisco.ios.ios_config task pushes a list of NTP and logging commands that already exist verbatim in the running-config, yet the play reports 'changed' on every execution and re-sends all commands, br
- A nightly compliance playbook that uses cisco.ios.ios_config must copy the running-config to startup-config on every execution to produce audit evidence, even when no line changed during the run. Whic
- A playbook uses cisco.ios.ios_config to push a banner and several service commands that already exist verbatim in the running-config, yet the team wants every command re-sent to the device on each run
- A network team must ensure that interface administrative state, descriptions, and MTU on Cisco IOS-XE switches converge to exactly the values defined in their source of truth, with any manual out-of-b
- An engineer must configure identical VTY line settings ('transport input ssh', 'exec-timeout 10 0', and 'logging synchronous') on a group of Cisco IOS-XE switches with cisco.ios.ios_config. The three
- ios_config is imperative; resource modules are declarative
For structured features (VLANs, OSPF, interfaces, ACLs) prefer the declarative resource modules (
ios_vlans,ios_ospfv2,ios_l3_interfaces,ios_acls) which model and converge state; reserveios_configfor raw CLI that no resource module covers.- network_cli, netconf, and httpapi connection plugins
Network devices set
ansible_connectiontoansible.netcommon.network_cli(SSH CLI),ansible.netcommon.netconf(NETCONF/830), oransible.netcommon.httpapi(REST/RESTCONF), andansible_network_os: cisco.ios.iosto select the platform parser and command set.8 questions test this
- An engineer builds an Ansible playbook that pushes VLAN and interface configuration to a fleet of Cisco IOS-XE switches over SSH using the cisco.ios resource modules. The inventory must instruct Ansib
- An engineer automates RESTCONF on Cisco IOS-XE with ansible_connection set to ansible.netcommon.httpapi. Modules exchange JSON, but every request is refused because traffic is sent as plaintext HTTP t
- An engineer stores ansible_network_os and shared credentials in group_vars/ios_xe.yml for a group of Cisco IOS-XE switches. One switch also has ansible_user defined in its host_vars file with a differ
- A playbook uses ansible.netcommon.network_cli against Cisco IOS-XE routers. The SSH login with ansible_user and ansible_password succeeds and show tasks work, but configuration tasks fail. The play se
- An engineer configures inventory for Cisco IOS-XE switches with ansible_connection set to ansible.netcommon.network_cli, but the play fails to correctly parse show command output and manage terminal l
- A playbook uses ansible.netcommon.network_cli to configure ACLs on Cisco IOS-XE routers. The SSH login with ansible_user and ansible_password succeeds, but tasks fail because the session remains in us
- An engineer manages a group of Cisco IOS-XE switches with Ansible. Most switches share the same credentials and ansible_network_os, but one lab switch uses a different password and must not have those
- A playbook manages OSPF on Cisco IOS-XE routers using the netconf_config module to push a YANG-modeled payload. The devices listen for management sessions on TCP 830. Which ansible_connection setting
- become_method enable reaches privileged config mode
For
network_cli,ansible_user/ansible_passwordauthenticate the SSH session andbecome: truewithbecome_method: enable(optionallyansible_become_password) elevates to privileged EXEC so configuration tasks can run.2 questions test this
- A playbook uses ansible.netcommon.network_cli against Cisco IOS-XE routers. The SSH login with ansible_user and ansible_password succeeds and show tasks work, but configuration tasks fail. The play se
- A playbook uses ansible.netcommon.network_cli to configure ACLs on Cisco IOS-XE routers. The SSH login with ansible_user and ansible_password succeeds, but tasks fail because the session remains in us
- group_vars and host_vars scope connection variables
Inventory groups let
group_vars/<group>.ymlset shared connection variables whilehost_vars/<host>.ymloverrides per device; this keeps credentials andansible_network_osout of playbooks and scoped correctly.2 questions test this
- An engineer stores ansible_network_os and shared credentials in group_vars/ios_xe.yml for a group of Cisco IOS-XE switches. One switch also has ansible_user defined in its host_vars file with a differ
- An engineer manages a group of Cisco IOS-XE switches with Ansible. Most switches share the same credentials and ansible_network_os, but one lab switch uses a different password and must not have those
- httpapi connection drives RESTCONF and controller APIs
The
httpapiconnection plugin (withansible_httpapi_use_ssl: trueandansible_httpapi_port: 443) is used for RESTCONF and controller APIs such as Cisco Catalyst Center, where modules exchange JSON over HTTPS rather than screen-scraping CLI.3 questions test this
- An engineer builds an Ansible playbook that pushes VLAN and interface configuration to a fleet of Cisco IOS-XE switches over SSH using the cisco.ios resource modules. The inventory must instruct Ansib
- An engineer automates RESTCONF on Cisco IOS-XE with ansible_connection set to ansible.netcommon.httpapi. Modules exchange JSON, but every request is refused because traffic is sent as plaintext HTTP t
- A playbook manages OSPF on Cisco IOS-XE routers using the netconf_config module to push a YANG-modeled payload. The devices listen for management sessions on TCP 830. Which ansible_connection setting
- ansible-vault encrypts secrets at rest with AES256
ansible-vault encryptprotects a var file with AES256 so credentials stay ciphertext in Git;ansible-vault encrypt_stringembeds a single encrypted value inline in an otherwise plaintext vars file for selective secrecy.4 questions test this
- During a code review an engineer needs to confirm the SNMP community string stored in an ansible-vault-encrypted vars file matches the value in the change ticket. The engineer must read the plaintext
- An engineer must rotate the Vault password on group_vars/prod/vault.yml because a departing team member knew the old passphrase. The file must remain encrypted with AES256 at rest, its contents must n
- A GitLab CE pipeline runs ansible-playbook to push OSPF configuration to Cisco IOS-XE routers. The variables are stored in an ansible-vault-encrypted file, and the plaintext secret must never be writt
- An engineer maintains host_vars/rtr1.yml containing many non-sensitive interface settings plus one sensitive enable_password. The file must stay human-readable YAML in Git for easy review, yet the ena
- Vault password is supplied only at runtime
The decryption key is provided at run time via
--ask-vault-passor--vault-password-file, and--vault-id label@sourcesupports multiple vaults; the plaintext secret never lives on disk or in source control.6 questions test this
- During a code review an engineer needs to confirm the SNMP community string stored in an ansible-vault-encrypted vars file matches the value in the change ticket. The engineer must read the plaintext
- An engineer runs a playbook interactively from a workstation to push VLAN changes; the vars file is ansible-vault encrypted. Company policy forbids storing the Vault password anywhere on the workstati
- A pipeline job fails immediately with the error 'Attempting to decrypt but no vault secrets found' when running a playbook that references an ansible-vault-encrypted vars file. The file decrypts corre
- An engineer manages one vars file encrypted with a development password and another encrypted with a production password. A single ansible-playbook invocation must decrypt both files in the same run,
- An engineer must rotate the Vault password on group_vars/prod/vault.yml because a departing team member knew the old passphrase. The file must remain encrypted with AES256 at rest, its contents must n
- A GitLab CE pipeline runs ansible-playbook to push OSPF configuration to Cisco IOS-XE routers. The variables are stored in an ansible-vault-encrypted file, and the plaintext secret must never be writt
- Storing the vault key in the repo defeats the purpose
Committing the vault password (or a symmetric key) alongside the encrypted file gives no real protection because anyone with repo access can decrypt; the key must come from an out-of-band source or a secrets manager at runtime.
Trap Encrypting the vars file but committing the vault password file next to it is a common secret-management anti-pattern.
- Idempotency means re-runs report zero changes
An idempotent task reports
changedonly when it actually altered device state; re-running a fully converged playbook against the same devices yieldsokwithchanged=0, which is the signal that the desired state already matches.3 questions test this
- During pre-change review before a maintenance window, an engineer must preview the exact VLAN and interface configuration a playbook would apply to production IOS-XE switches, seeing the precise line-
- An engineer runs a fully converged Ansible playbook built on cisco.ios resource modules a second time against the same IOS-XE switches to confirm no drift has occurred; nothing on the devices changed
- An engineer refactors a monolithic NTP configuration playbook into a reusable role for IOS-XE devices. Several tasks may modify the NTP configuration, and the NTP service must restart only when the co
- Check mode previews changes without applying them
Running a playbook with
--check(check mode) has resource modules compute and report the changes they would make without pushing anything, and--diffshows the exact config delta; together they give a safe dry-run before a real apply.- Roles standardize layout; handlers run once when notified
A role uses the standard
tasks/ handlers/ templates/ vars/ defaults/ meta/layout for reuse; a handler runs once at the end of the play and only when a task thatnotifys it reportedchanged, ideal for a single save or service reload.2 questions test this
- An engineer runs a fully converged Ansible playbook built on cisco.ios resource modules a second time against the same IOS-XE switches to confirm no drift has occurred; nothing on the devices changed
- An engineer refactors a monolithic NTP configuration playbook into a reusable role for IOS-XE devices. Several tasks may modify the NTP configuration, and the NTP service must restart only when the co
- Jinja2 templates and loops render per-device config
Jinja2 templates plus
loop/with_itemsrender device-specific values (VLAN IDs, ACL entries, OSPF areas) from inventory variables, andwhenconditionals gate tasks so one playbook adapts across a heterogeneous fleet.- Ansible block/rescue/always enables change rollback
In Ansible, block groups tasks; rescue runs its tasks only if a task in the block fails (place restore/undo steps here); always runs regardless — together they give a change task automated rollback when a push or post-change verify fails, instead of leaving the device half-configured.
Trap Believing a failed task automatically rolls back prior changes without a rescue block.
- failed_when and ignore_errors shape failure handling
failed_when defines a custom failure condition on a task (e.g. fail when command output contains 'Invalid'), and ignore_errors: true lets the play continue past a failure so a later cleanup or rescue can run — both control when the recovery path triggers.
Trap Confusing ignore_errors (continue past failure) with failed_when (redefine what counts as failure).
2 questions test this
- An engineer needs a playbook that applies an ACL change to IOS-XE routers, and if any task in the change set fails, automatically restores the previous ACL, while always logging the final outcome whet
- An engineer's playbook runs cisco.ios.ios_command against IOS-XE routers to execute a custom verification command. The device returns success (rc=0) even when the CLI prints 'Invalid input detected',
- cisco.ios.ios_facts collects hardware/software inventory
cisco.ios.ios_facts gathers device facts into ansible_net_* variables (ansible_net_model, ansible_net_serialnum, ansible_net_version, ansible_net_hostname) — the standard way to collect hardware/software inventory for an asset database; gather_subset tunes what is collected.
Trap Expecting a resource module's 'gathered' state to return hardware serial/model — it returns per-feature CONFIG, not asset inventory.
2 questions test this
- An engineer must enrich a NetBox source-of-truth with the hardware model, chassis serial number, and running software version for 350 Cisco IOS-XE switches. The play must collect this inventory withou
- An engineer must build an asset inventory report listing the model number, serial number, software version, and hostname for 200 Cisco IOS-XE switches. The playbook should gather this data idempotentl
- RESTCONF content=nonconfig returns operational state
A RESTCONF GET with query content=nonconfig returns operational (read-only) state such as interface oper-status and counters, while content=config returns only configuration and omitting content returns both — how live device state is read without changing config.
Trap Assuming a plain RESTCONF GET on a data node returns only configuration data.
3 questions test this
- An engineer scripts an ncclient session to a Cisco IOS-XE device to read live LLDP neighbor state and interface uptime for an asset database. A first attempt built on a <get-config> RPC against the ru
- An engineer must enrich a NetBox source-of-truth with the hardware model, chassis serial number, and running software version for 350 Cisco IOS-XE switches. The play must collect this inventory withou
- A Python client uses a RESTCONF GET (RFC 8040) against a Cisco IOS-XE router to read the live operational status and packet counters of interfaces from the ietf-interfaces-state model, and must exclud
- NETCONF returns config+state, config only
NETCONF returns configuration AND operational state, whereas returns configuration only from a named datastore — use to read operational/asset data such as counters or neighbor state.
Trap Using to read operational counters (it returns configuration only).
2 questions test this
- An engineer scripts an ncclient session to a Cisco IOS-XE device to read live LLDP neighbor state and interface uptime for an asset database. A first attempt built on a <get-config> RPC against the ru
- An engineer scripts an ncclient session to a Cisco IOS-XE device to retrieve interface operational counters and LLDP neighbor state for an asset database, using a single RPC. Configuration-only data f
- Python retrieves and parses live device state
Python reads live device state by issuing ncclient get() RPCs or netmiko send_command show output, then parsing it into structured data with a Genie/pyATS parser or TextFSM for programmatic asset/state processing rather than screen-scraping raw text.
Trap Screen-scraping raw show output instead of parsing it into structured data.
4 questions test this
- A developer issues an ncclient get() RPC to a Cisco IOS-XE device and receives interface operational state as a NETCONF reply. The asset pipeline needs a Python dictionary keyed by interface name with
- An engineer scripts an ncclient session to a Cisco IOS-XE device to retrieve interface operational counters and LLDP neighbor state for an asset database, using a single RPC. Configuration-only data f
- A Python client uses a RESTCONF GET (RFC 8040) against a Cisco IOS-XE router to read the live operational status and packet counters of interfaces from the ietf-interfaces-state model, and must exclud
- A developer writes a Python script with Netmiko to collect CDP neighbor and interface data from Cisco IOS-XE switches for a source-of-truth import. The show command output must become structured key/v
References
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_config_module.html
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_vlans_module.html
- https://docs.ansible.com/ansible/latest/network/user_guide/network_resource_modules.html
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_acls_module.html
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_ospfv2_module.html
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_interfaces_module.html
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_l3_interfaces_module.html
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_command_module.html
- https://docs.ansible.com/ansible/latest/network/user_guide/platform_ios.html
- https://www.rfc-editor.org/rfc/rfc6241
- https://www.rfc-editor.org/rfc/rfc8040
- https://developer.cisco.com/docs/dna-center/
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html
- https://docs.ansible.com/ansible/latest/vault_guide/vault.html
- https://docs.ansible.com/ansible/latest/vault_guide/vault_encrypting_content.html
- https://docs.ansible.com/ansible/latest/vault_guide/vault_managing_passwords.html
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_checkmode.html
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_templating.html
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_blocks.html
- https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html
- https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_facts_module.html
- https://ncclient.readthedocs.io/en/latest/manager.html
- https://developer.cisco.com/docs/pyats/