Full Config Replace
Configuration Session
  • Introduction
  • Dev Setup
  • NX-API Overview
  • NX-API Python
  • Ansible NXOS
  • pyATS
  • NetDevOps
  • Configuration Session
  • Terraform
  • Bonus: YANG
  • Reference: Postman

Driving a Configuration Session with Ansible

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:

  1. Generate a unique candidate-session name (golden_session_<random>) so the playbook is re-runnable.
  2. Save the device's current running configuration to bootflash:golden_config.cfg as a "golden" baseline.
  3. Initialize the named candidate session.
  4. Factory-reset the candidate so the import is a true full-config replace.
  5. Import bootflash:golden_config.cfg into the candidate session.
  6. Diff the candidate session against the running configuration and display the result.
  7. Pause the playbook and wait for you to type yes to commit, or anything else to abort.
  8. Commit (or abort) the session, then list the configuration commits on the device.

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.

Note — Why this play uses SSH (network_cli) instead of NX-API (httpapi)

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.


Step 1 - Modules You Will Use

Two Ansible modules do all the work in this section:

  • cisco.nxos.nxos_command — runs arbitrary commands on an NXOS device and returns the output. You will use it both for copy / candidate-session commands and for show commands whose output you want to capture and display. Reference: cisco.nxos.nxos_command documentation.

  • ansible.builtin.pause — pauses the playbook and (optionally) prompts the user for input. The user's typed response is exposed on 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.

Step 2 - Create the 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/pod22/workspace/nxapilab/ansible-nxos


touch /home/pod22/workspace/nxapilab/ansible-nxos/config-session.yml
cat <<'EOF' > /home/pod22/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:

  • The play-level 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.

  • The first task uses 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.

  • The save-to-bootflash task uses 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.

  • The candidate-init task bundles three commands — 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.

  • The commit and abort tasks both use the outside-session form, 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)#).

Step 3 - Run the Playbook Against 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


Step 4 - Review the Diff Output

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"
    ]
}

Step 5 - Approve (or Abort) the Commit

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
: 
  • If you type yes and press Enter, the Commit the candidate session task runs and the Abort task is skipped.

  • If you type anything else (or just press Enter with no input), the Abort task runs and the Commit task is skipped — leaving the running-config untouched and cleaning up the candidate session on the switch.

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.


Step 6 - Review the Commit List

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.


Step 7 - Recap

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.