Ansible – Cisco IOS upgrades

I recently had the pleasure of working on a project which required the deployment of configurations to startup-config and updating IOS images on a bi-annual basis to around 1000 production switches. This was achieved with tool called CatTools. I’ve never come across this tool before, but I’m sure at the time it was the right one for the job but after using it a few times the tedium factor was turned up to eleven! Want to load a config? Select the following: device files, activity, tftp source folder. Want to load a new IOS? Select a new activity and different tftp folder. Then after all this MD5 verify the switch contents and reboot. This involved a heap user interaction and absolutely no intelligence on behalf of the tool.

Before I left the contract I decided it was a good opportunity to learn about ansible, write a python script to produce and inventory/ group_vars and also a playbook. Below is snippet of these elements which give a great ansible example for IOS upgrades.

For this example we will be working on two different platforms (887 and 3750X) requiring two group_var files with slightly differing YAML dictionary values for filenames and MD5 hashes. We can then dynamically pluck this out in the playbook. The lesson here is the more specific information we can include in the either the inventory of group information the less user interaction required!

# ./hosts.ini

R01 ansible_host=

SW01 ansible_host=
SW02 ansible_host=
# ./group_vars/routers887.ini
ansible_network_os: ios
ios_file: c880data-universalk9-mz.154-3.M10.bin
ios_md5: 282035991bb0acf3ad01f7be58edb577
boot_file_loc: flash
# ./group_vars/switches3750x.ini
ansible_network_os: ios
ios_file: c3750e-universalk9-mz.152-4.E8.bin
ios_md5: e5fba1c9dd68da71f649c48874557d5b
boot_file_loc: flash

The play is broken down into 5 tasks. Three of the task a dependent on boolean conditions being met before being run, using the logic increases the speed at which the playbook can be run over a large inventory of devices.

  • Is file present‘ always run. It checks the device flash for the presence of the desired IOS binary file and assigns the output to a variable var_file_present. If the binary is not present the variable will be a string of length zero.
  • Load IOS‘. Executes only if var_file_present has a length of zero. Copies the IOS binary specified in the relevant group_var file from FTP server and saves it to the group_var defined flash location.
  • Final MD5 check‘. By this point either the IOS binary was already present or it has just been copied to the device. As a sanity check gather the MD5 hash of the file and assign it to variable var_ios_md5_final .
  • Change boot var‘. Only change the boot system setting if var_ios_md5_final matches the MD5 hash specified by the relevant group_var file.
  • Reload device‘ . Reload in 5 only if the MD5 sums match and the user indicated it wanted devices to be reloaded.
# ./play-upgrade_ios.yml
- name: Switch activities FULL
    - routers887
  connection: network_cli
  gather_facts: no
  become: yes
  become_method: enable
  strategy: linear

    - name: "reboot_bool"
      prompt: "Reboot the devices at end of play? (Y or N)"
      private: no
    - name: "server_ip"
      prompt: "IP address of this host"
      private: no

    - name: Is file present
        commands: "sh {{ boot_file_loc }}: | inc {{ ios_file }}"
      register: var_file_present

    - name: Load IOS
          - command: "copy ftp://user:some_pass@{{ server_ip }}/{{ ios_file }} {{ boot_file_loc }}:"
            prompt: "]?"
            answer: "\r"
        ansible_command_timeout: 600
      when: var_file_present.stdout[0] == ""

    - name: Final MD5 check
        commands: "verify /md5 {{ boot_file_loc }}:{{ ios_file }}"
        ansible_command_timeout: 60
      register: var_ios_md5_final  

    - name: Change boot var
          - no boot system
          - boot system {{ boot_file_loc }}:{{ ios_file }}
        save_when: always
      when: ios_md5 == var_ios_md5_final.stdout[0].split(' = ')[1]

    - name: Reload device
          - command: reload in 5
            prompt: "]?"
            answer: "\r"
        - reboot_bool == "Y"
        - ios_md5 == var_ios_md5_final.stdout[0].split(' = ')[1]

Below is the output of the play. In this scenario R01 already has the correct IOS file present on flash but R02 does not.

sr@thinker:~/ansible_stuff/config-if_post$ ansible-playbook -i hosts.ini -u admin -k -K ./play-upgrade_ios.yml
SSH password: 
BECOME password[defaults to SSH password]: 
Reboot the devices at end of play? (Y or N): N
IP address of this host:

PLAY [Switch activities FULL] **********************************

TASK [Is file present] *****************************************
ok: [R01]
ok: [R02]

TASK [Load IOS] ************************************************
skipping: [R01]
ok: [R02]

TASK [Final MD5 check] *****************************************
ok: [R02]
ok: [R01]

TASK [Change boot var] *****************************************
changed: [R01]
changed: [R02]

TASK [Reload device] *******************************************
skipping: [R01]
skipping: [R02]

PLAY RECAP *****************************************************
R01  : ok=3    changed=1    unreachable=0    failed=0    
       skipped=2    rescued=0    ignored=0   
R02  : ok=4    changed=1    unreachable=0    failed=0    
       skipped=1    rescued=0    ignored=0   

Note how R01 skips the ‘Load IOS’ task, but both must perform the MD5 hash generation step and reconfigure the boot variable regardless.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at

Up ↑

%d bloggers like this: