Ansible NAPALM
Why NAPALM + Ansible?
NAPALM gives you a vendor‑agnostic Python API for interacting with network devices, while Ansible gives you idempotent automation driven by human‑readable YAML.\ Together they let you:
- Retrieve facts or configuration from many vendors with a single module call.
- Safely push configuration changes with automatic diff and rollback.
- Validate compliance against source‑of‑truth data before (or after) a change window.
A note on the old napalm
connection plugin
The ansible.netcommon.napalm
connection plugin was deprecated and removed from the collection starting in ansible.netcommon 5.0.\
Modern playbooks should instead use the dedicated NAPALM modules (get_facts
, install_config
, validate
, and friends) which keep the NAPALM logic self‑contained.
Prerequisites
Component | Recommended Version | Notes |
---|---|---|
Python | 3.10+ | NAPALM and Ansible now test primarily on 3.9+ |
Ansible Core | 2.16+ | Install from pip install ansible-core |
ansible‑napalm collection | latest | ansible-galaxy collection install napalm.napalm |
Python NAPALM library | 4.x | pip install napalm |
Network devices | IOS‑XE 17+, Junos 22+, EOS 4.30+, … | Anything supported by NAPALM |
If you prefer Docker, grab the ready‑made devcontainer from the GitHub repo.
Installing everything in one go
python3 -m venv .venv
source .venv/bin/activate
pip install ansible-core napalm
ansible-galaxy collection install napalm.napalm
Tip: Pin exact versions in a
requirements.txt
andcollections/requirements.yml
so your CI pipeline is repeatable.
Project layout
inventory/
routers.yml
group_vars/
all.yml
playbooks/
gather-facts.yml
backup-config.yml
push-config.yml
validate/
bgp.yml
Inventory example
inventory/routers.yml
:
all:
children:
lab_routers:
hosts:
r1:
ansible_host: 192.0.2.11
ansible_user: ansible
ansible_password: Cisco123
ansible_network_os: ios
r2:
ansible_host: 192.0.2.12
ansible_user: ansible
ansible_password: Juniper123
ansible_network_os: junos
Global variables (group_vars/all.yml)
napalm_username: "{{ ansible_user }}"
napalm_password: "{{ ansible_password }}"
napalm_optional_args:
global_delay_factor: 2
1. Gathering device facts
Create playbooks/gather-facts.yml
:
---
- name: Collect baseline facts with NAPALM
hosts: lab_routers
gather_facts: no
tasks:
- name: Get facts (basic example)
napalm.napalm.get_facts:
hostname: "{{ inventory_hostname }}"
username: "{{ napalm_username }}"
password: "{{ napalm_password }}"
optional_args: "{{ napalm_optional_args | default({}) }}"
register: result
- name: Show facts
debug:
var: result.facts
# ---
# Advanced example: Using filters, args, and assertions
- name: Get advanced facts and validate
hosts: lab_routers
gather_facts: no
tasks:
- name: Get filtered facts from device
napalm.napalm.get_facts:
hostname: "{{ inventory_hostname }}"
username: "{{ napalm_username }}"
password: "{{ napalm_password }}"
optional_args:
path: "{{ playbook_dir }}/mocked/{{ inventory_hostname }}"
profile: "{{ profile | default('') }}"
filter:
- facts
- route_to
- interfaces
args:
route_to:
protocol: static
destination: 8.8.8.8
register: test_napalm
- name: Assert on returned facts
assert:
that:
- test_napalm.facts.hostname is defined
- test_napalm.facts.interfaces is defined
# Add more assertions as needed, e.g.:
# - test_napalm.facts.hostname == "expected-hostname"
# - '1.0.4.0/24' in test_napalm.facts.route_to
Run it:
Sample output:
ok: [r1] => {
"result.facts": {
"hostname": "R1",
"model": "CSR1000v",
"os_version": "17.09.04",
"serial_number": "9B0FD12ZABCDEFG",
...
}
}
2. Backing up running‑config
playbooks/backup-config.yml
:
---
- name: Save configs to local backup directory
hosts: lab_routers
gather_facts: no
tasks:
- name: Fetch running config
napalm.napalm.get_facts:
filter: config
hostname: "{{ inventory_hostname }}"
username: "{{ napalm_username }}"
password: "{{ napalm_password }}"
register: config
- name: Write file locally
copy:
content: "{{ config.facts.config.running }}"
dest: "backups/{{ inventory_hostname }}-{{ lookup('pipe', 'date +%F') }}.cfg"
Idempotency note: NAPALM modules set
changed: false
when there is no diff, so you can chain them in CI pipelines without side‑effects.
3. Pushing configuration safely
Create a candidate config file, e.g. configs/r1_bgp.txt
:
Playbook playbooks/push-config.yml
:
---
- name: Push candidate config with automatic diff + rollback
hosts: r1
gather_facts: no
tasks:
- name: Install configuration
napalm.napalm.install_config:
hostname: "{{ ansible_host }}"
username: "{{ napalm_username }}"
password: "{{ napalm_password }}"
optional_args: "{{ napalm_optional_args | default({}) }}"
candidate_filename: "configs/{{ inventory_hostname }}_bgp.txt"
commit_changes: true
replace_config: false
diff_file: "diffs/{{ inventory_hostname }}-{{ lookup('pipe', 'date +%F-%H%M%S') }}.diff"
What happens next:
- NAPALM loads the candidate file into the device’s compare engine.
- If the diff is non‑empty, Ansible sets
changed: true
and commits. - On error, NAPALM triggers an automatic rollback, so you stay safe.
4. Validating state against source‑of‑truth
NAPALM ships a YAML‑based validation framework.
validate/bgp.yml
:
---
bgp_neighbors:
global:
router_id: 192.0.2.1
peers:
203.0.113.1:
is_up: true
address_family:
ipv4 unicast:
received_prefixes: 1
playbooks/validate.yml
:
---
- name: Check operational state
hosts: r1
gather_facts: no
tasks:
- name: Validate BGP
napalm.napalm.validate:
hostname: "{{ ansible_host }}"
username: "{{ napalm_username }}"
password: "{{ napalm_password }}"
validation_file: "validate/bgp.yml"
register: validate
- name: Fail if non‑compliant
assert:
that:
- validate.compliance
Add this to your CI/CD pipeline and you have continuous compliance testing.
5. Putting it all together in CI
name: network-ci
on:
push:
paths:
- "configs/**"
- "validate/**"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install ansible-core napalm
ansible-galaxy collection install -r collections/requirements.yml
- name: Dry‑run config
run: ansible-playbook -i inventory/ playbooks/push-config.yml --check
- name: Validate
run: ansible-playbook -i inventory/ playbooks/validate.yml
Troubleshooting checklist
Symptom | Fix |
---|---|
ModuleNotFoundError: napalm |
Activate your virtualenv or pip install napalm |
ssh_exchange_identification |
Check ansible_host IP and ACLs; NAPALM needs SSH access |
Device shows changed every run | Use replace_config: false and make device config canonical |
Connection plugin not found | Make sure you don’t set ansible_connection: napalm after its removal |
Next steps
- Explore vendor‑specific optional args (e.g. EOS
transport: https
) in the NAPALM docs. - Combine NAPALM with Ansible’s
delegate_to:
for control‑plane orchestration. - Look at
napalm_diff_yang
for data‑model‑driven compliance.
Happy automating! If you spot any issues, open a pull request or ping me on Bluesky
Updated: 27 June 2025