Ansible for Network Engineers
Idempotent Modules, Loops, Conditionals, and Check Mode
What you'll learn
- What cisco.ios.ios_config and cisco.ios.ios_command actually do, and what installing cisco.ios requires
- How loop: replaces manually repeating a task once per VLAN
- How when: conditionally runs a task based on data
- How --check and --diff map directly onto the dry_run=True pattern from Reliable Automation
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
Real Cisco modules, and what they need
Two cisco.ios modules matter most for this module's capstone:
cisco.ios.ios_command— runs read-only show commands, the same rolesend_command()played with Netmiko.cisco.ios.ios_config— pushes configuration-mode lines, the same rolesend_config_set()played — but idempotently: it checks the device's current configuration first, and only sends lines that would actually change something.
Requires the cisco.ios collection, and a real device to run for real
Both modules live in the cisco.ios collection (ansible-galaxy collection install cisco.ios — Lesson 2), not ansible-core itself. Running either one for real requires a real or virtual Cisco device (Option A). Neither module's examples in this lesson were run against a live device in this authoring environment — see the callout below.
- name: Check current VLANs before changing anything
hosts: switches
gather_facts: false
tasks:
- name: Gather current VLAN brief
cisco.ios.ios_command:
commands:
- show vlan brief
register: vlan_outputRead-only, same as every 'check first' example since Netmiko Fundamentals Lesson 3. register: saves the command's output into a variable for a later task to use.
ios_config: idempotent by design
- name: Ensure VLAN 10 exists
hosts: switches
gather_facts: false
tasks:
- name: Create VLAN 10
cisco.ios.ios_config:
lines:
- "vlan 10"
- "name USERS"
parents: "vlan 10"lines are the configuration-mode commands to ensure are present; parents names the configuration context they belong under. This single task is documented to check the device's current config first and only send lines that would actually change something — the exact check-before-change apply_ntp_config() implemented by hand in Reliable Automation, now a framework guarantee.
This mirrors apply_ntp_config() directly
Reliable Automation's apply_ntp_config() read the current config, compared it against what's required, and only pushed a change if something was actually missing. ios_config is documented to do exactly that internally — the hand-written pre-check/decide/apply pattern from Phase 2.8, now built into the module itself.
loop: one task, many VLANs
Configuration & Template Automation's capstone needed a {% for %} loop inside a template to render multiple VLANs. A playbook needs the same repetition at the task level — loop: runs one task once per item in a list:
- name: Ensure required VLANs exist
hosts: switches
gather_facts: false
vars:
required_vlans:
- { id: 10, name: USERS }
- { id: 20, name: VOICE }
tasks:
- name: Create each required VLAN
cisco.ios.ios_config:
lines:
- "vlan {{ item.id }}"
- "name {{ item.name }}"
parents: "vlan {{ item.id }}"
loop: "{{ required_vlans }}"loop: runs this one task once per item in required_vlans, with item bound to the current VLAN dict each time — the exact same mechanic as Configuration & Template Automation's {% for vlan in vlans %}, now looping a task instead of a block of rendered text.
when: conditional tasks
when: skips a task unless a condition is true — the playbook-level equivalent of the {% if %} conditional already used inside a template:
- name: Only touch devices that actually need this VLAN
cisco.ios.ios_config:
lines:
- "vlan {{ item.id }}"
- "name {{ item.name }}"
parents: "vlan {{ item.id }}"
loop: "{{ required_vlans }}"
when: item.id is definedwhen: item.id is defined skips the task entirely for any loop item missing an id — the same 'don't trust the data blindly' discipline validate_rendered_config() applied to rendered text, now applied before a task even runs.
Check mode and diff: Ansible's own dry-run
Reliable Automation's entire safety model rested on one flag: dry_run=True, on by default, guaranteeing nothing real happens unless explicitly turned off. Ansible has a direct equivalent, built into the framework itself:
- dry_run=Truethe function's own default parameter
- Preview onlyno send_config_set() call happens
- dry-run-preview result state
- ansible-playbook --checka command-line flag, not a code default
- Preview onlymodules report what WOULD change, without changing it
- --diff shows the exact before/after lines
$ ansible-playbook ensure_required_vlans.yml --check --diff
TASK [Create each required VLAN] ****************************************
--- before
+++ after
@@ -1,3 +1,5 @@
!
+vlan 20
+ name VOICE
changed: [SW2] => (item={'id': 20, 'name': 'VOICE'})
PLAY RECAP ****************************************************************
SW2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0--check runs the entire playbook without applying any real change; --diff additionally shows the exact lines that would be added. This describes Ansible's own documented check-mode output format — no device was actually touched to produce this.
Syntax success is not the same as a successful device change
ansible-playbook --syntax-check only confirms the playbook's YAML and schema are valid — it says nothing about whether a real device would accept the commands. --check goes further, simulating what modules report they would do — but for network modules, that simulation is still based on what the module believes the device's state is, not a guarantee. Only a real run against a real device, with a real post-check, confirms a change actually succeeded — exactly the distinction Reliable Automation drew between "the script ran without error" and "the change was verified."
Key takeaways
- cisco.ios.ios_command (read-only) and cisco.ios.ios_config (idempotent config changes) require the cisco.ios collection and a real device to run for real — neither was executed against a device in this authoring environment.
- ios_config is documented to check-before-change internally — the same pattern apply_ntp_config() implemented by hand in Reliable Automation, now a framework guarantee.
- loop: repeats one task per item in a list — the task-level equivalent of a template's {% for %} loop.
- when: conditionally skips a task per loop item — the task-level equivalent of a template's {% if %}.
- ansible-playbook --check (with --diff) is Ansible's own dry-run — the direct framework equivalent of Reliable Automation's dry_run=True default. Syntax success, check-mode success, and a real verified device change are three distinct claims, not one.