In the previous section, you walked through the Configuration Session lifecycle conceptually and at the CLI. In this
section, you will drive that same lifecycle from an Ansible playbook against your staging-leaf1
switch using the cisco.nxos.nxos_command module and the ansible.builtin.pause module. The playbook
will:
golden_session_<random>) so the playbook is re-runnable.bootflash:golden_config.cfg as a "golden" baseline.bootflash:golden_config.cfg into the candidate session.yes to commit, or anything else to abort.This pattern — save current state → build candidate → diff → human approves → commit — is the exact gated workflow that makes Configuration Session valuable in an automated pipeline: the network engineer is the final gate before any change reaches the running configuration.
Earlier in this lab, the Ansible connection plugin was set to ansible.netcommon.httpapi in
group_vars/nxos/connection.yml, which sends each CLI command as a stateless NX-API call. Configuration
Session is a stateful CLI feature — commands like factory-reset candidate and
import configuration bootflash:… are only valid while you are inside the candidate session prompt
(switch(config-c-<session>)#). Over httpapi, that prompt context is not preserved between commands, so
those commands fail with % Invalid command.
To drive Configuration Session end-to-end from Ansible, this play overrides the connection plugin to
ansible.netcommon.network_cli (SSH) at the play level using a vars: block. With network_cli,
a single SSH session is held open for the duration of the play and the prompt context flows naturally across the
commands inside one nxos_command task — exactly like an interactive operator session.
The play uses a per-run unique named candidate session (golden_session_<random>, generated with
set_fact) and the "outside candidate session" commit/abort form
(configure candidate name <session> [commit | abort]) so that the commit and abort tasks do not depend
on whatever submode prompt the import left behind, and so the playbook can be re-run without colliding with previously
committed session names.
Two Ansible modules do all the work in this section:
copy / candidate-session commands and for show commands whose output you want to
capture and display. Reference:
cisco.nxos.nxos_command documentation.
register'd variable's user_input attribute, which is exactly what you
need to gate the commit on a typed yes. Reference:
ansible.builtin.pause documentation.
config-session.yml Playbook
Return to your VSCode Terminal window and make sure you are in your ansible-nxos directory. Create a new playbook
called config-session.yml that contains all of the tasks for this lab.
cd /home/pod19/workspace/nxapilab/ansible-nxos
touch /home/pod19/workspace/nxapilab/ansible-nxos/config-session.yml
cat <<'EOF' > /home/pod19/workspace/nxapilab/ansible-nxos/config-session.yml
---
- name: Configuration Session demo on staging-leaf1
hosts: leafs
gather_facts: false
vars:
ansible_connection: ansible.netcommon.network_cli
ansible_user: "{{ lookup('ansible.builtin.env', 'NXOS_USERNAME') }}"
ansible_password: "{{ lookup('ansible.builtin.env', 'NXOS_PASSWORD') }}"
ansible_host_key_checking: false
tasks:
- name: Generate unique candidate-session name for this run
ansible.builtin.set_fact:
session_name: "golden_session_{{ lookup('pipe', 'date +%s') }}"
run_once: true
delegate_to: localhost
vars:
ansible_connection: local
- name: Show the session name that will be used
ansible.builtin.debug:
msg: "Using candidate session name: {{ session_name }}"
- name: Save running-config to bootflash:golden_config.cfg
cisco.nxos.nxos_command:
commands:
- command: copy running-config bootflash:golden_config.cfg
prompt: '\(y/n\)'
answer: 'y'
- name: Open candidate session, factory-reset, and import golden config
cisco.nxos.nxos_command:
commands:
- "configure candidate name {{ session_name }}"
- factory-reset candidate
- import configuration bootflash:golden_config.cfg
- end
- name: Show candidate-config diff vs running-config
cisco.nxos.nxos_command:
commands:
- "show candidate-config name {{ session_name }} diff"
register: diff_result
- name: Display the diff for review
ansible.builtin.debug:
msg: "{{ diff_result.stdout_lines[0] }}"
- name: Pause for human approval
ansible.builtin.pause:
prompt: |
Review the candidate-config diff above.
Type 'yes' to COMMIT the session, anything else to ABORT
register: user_decision
- name: Commit the candidate session
cisco.nxos.nxos_command:
commands:
- "configure candidate name {{ session_name }} commit"
when: user_decision.user_input | lower == 'yes'
- name: Abort the candidate session
cisco.nxos.nxos_command:
commands:
- command: "configure candidate name {{ session_name }} abort"
prompt: '\(y/n\)'
answer: 'y'
when: user_decision.user_input | lower != 'yes'
- name: Show configuration commit list
cisco.nxos.nxos_command:
commands:
- show configuration commit list
register: commit_list
- name: Display the commit list
ansible.builtin.debug:
msg: "{{ commit_list.stdout_lines[0] }}"
EOF
A few things worth highlighting in the playbook:
vars: block overrides the group-level ansible.netcommon.httpapi connection
plugin (set in group_vars/nxos/connection.yml) with ansible.netcommon.network_cli. This is
required because Configuration Session is a stateful CLI feature — commands like factory-reset candidate
and import configuration bootflash:… are only valid while the device prompt is inside the candidate
session, and that prompt context only persists across commands when running over a single SSH session.
set_fact with the pipe lookup running date +%s on the control
node to generate a per-run candidate-session name of the form golden_session_<epoch>. NXOS reserves a
session name in its commit history once that session has been committed, so a second run with a fixed name fails with
Configure session with same name exists in committed state, new request refused. Using the current epoch
timestamp produces a unique name on every run and sidesteps that error. The fact is created with
run_once: true and delegate_to: localhost so all hosts share one name within a run, and so the
timestamp comes from the control node rather than a per-host SSH session.
nxos_command's prompt / answer arguments to
conditionally answer y if the device prompts Do you want to overwrite (y/n)?[n] when
bootflash:golden_config.cfg already exists. On a first run the prompt never appears and the command simply
completes; on subsequent runs the prompt appears and is auto-answered. nxos_command's prompt
is conditional, so it works for both cases.
configure candidate name {{ session_name }},
factory-reset candidate, and import configuration bootflash:golden_config.cfg — into a
single commands: list. Because the play uses network_cli (SSH), all three commands run inside the same
SSH session, which keeps the candidate-session prompt context intact and lets factory-reset candidate and
import configuration resolve correctly.
register: user_decision on the pause task captures the user's typed input as
user_decision.user_input. The commit task runs only when that value (lowercased) equals
yes; otherwise the abort task runs, ensuring the candidate session is never left orphaned on
the device.
configure candidate name {{ session_name }} [commit | abort], which is the form documented in the
previous section's lifecycle table for operating on a named session from outside it. This avoids depending on whatever
submode prompt the import left behind (e.g. (config-c-<session>-evpn-evi)#).
staging-leaf1
Run the playbook with ansible-playbook, using your existing staging.yml inventory and limiting
execution to staging-leaf1 with the --limit flag. This ensures the Configuration
Session work happens only on that one switch even though the leafs group contains all three leaf switches.
ansible-playbook -i staging.yml config-session.yml --limit staging-leaf1
When the Display the diff for review task runs, you should see Ansible print the candidate-config diff between the
candidate session and the running configuration. Because the candidate was factory-reset and then re-imported from a snapshot
of the same running-config, the diff should be small — in this run, the only delta is a single line
(copp profile strict) that NXOS re-applies as part of the import. Sample output (yours will differ depending on
what is currently configured on staging-leaf1):
TASK [Display the diff for review] **********************************************************************************************************************
ok: [staging-leaf1] => {
"msg": [
"--- running-config",
"+++ candidate-config",
"@@ -28,6 +28,7 @@",
" username cisco password 5 $5$EMAGFL$JtTEzvznXfVd9Q62u3KPH/ydnAxzuRxhirCIgWKgUrC role network-admin",
" username cisco passphrase lifetime 99999 warntime 14 gracetime 3",
" ip domain-lookup",
"+copp profile strict",
" snmp-server user admin network-admin auth md5 480FBFC4E084C3022D8CEB49A99B826E1052 priv aes-128 057FC3ECA2AAB8612A8CBE72E980F749233C localizedV2key",
" snmp-server user cisco network-admin auth md5 5203D5321877CE3B02A4CE5F3CB60D6D6FBE priv aes-128 041E8E3B140D857255E6B07127A117720FC7 localizedV2key",
" rmon event 1 log trap public description FATAL(1) owner PMON@FATAL"
]
}
Immediately after the diff is displayed, the playbook will hit the ansible.builtin.pause task and stop, prompting
you for a decision:
TASK [Pause for human approval] ************************************************************************************************************************** [Pause for human approval] Review the candidate-config diff above. Type 'yes' to COMMIT the session, anything else to ABORT :
yes and press Enter, the Commit the candidate session task runs and the
Abort task is skipped.Because the playbook gates the commit on your input, this same playbook can be safely re-run in a CI/CD pipeline with an interactive job — or wrapped to read 'yes' from an external approval system — without ever risking an unreviewed change to the running configuration.
The final task runs show configuration commit list and prints the result. If you typed yes in the
previous step, you should see a new commit at the top of the list with your golden_session session name and a
fresh commit ID. Sample output:
TASK [Display the commit list] ***************************************************************************************************************************
ok: [staging-leaf1] => {
"msg": [
"SNo. Label/ID User Line Client Time Stamp Session Name",
"---- ------------ ------------ ------------ ---------- ----------------------- -------------",
"1 2000000001 admin /dev/pts/4 CLI Tue May 26 04:21:11 2026 golden_session_1779769243"
]
}
Each entry in this list is a candidate point to roll back to using
rollback configuration commit <id>, exactly as covered in the lifecycle table on the previous page.
In this section you used Ansible to drive the full Configuration Session lifecycle from a single playbook:
save → init → factory-reset → import → diff → human approval → commit/abort → commit list.
The same building blocks — cisco.nxos.nxos_command with a list of candidate-session commands, plus
ansible.builtin.pause for the human gate — can be used to wrap any change you would otherwise make
directly on the switch, giving you atomic commits, a reviewable diff, and a rollback path on every change.