05. August 2018 · Comments Off on Ansible playbook to handle IOS upgrades. · Categories: Ansible, Cisco, Linux, Networking · Tags: , , , ,

The following is an Ansible playbook I created to handle IOS upgrades on against an excessively large number of Cisco routers at a customer site I was doing some work at. I saved a lot of time by staging the IOS images on flash before kicking off the playbook, if I missed anything this playbook would of uploaded the image for me before setting the boot statement. I think moving forward I will start leveraging the NTC (Network to Code) Ansible modules a lot more, its have proven itself to be superior and more feature rich than the built in Cisco Ansible modules.

In addition to the NTC requirements, this playbook also requires 2 directories:

  • ./images: directory that contains IOS images.
  • ./backups: directory repository for config backups.

ansible.cfg: ASCII text

[defaults]
transport = ssh
host_key_checking = false
retry_files_enabled = false
#stdout_callback = unixy
#stdout_callback = actionable
display_skipped_hosts = false
 
timeout = 5
 
inventory = ./hosts
log_path   = ./ansible.log
 
[ssh_connection]
pipelining = True

platform_facts.csv: ASCII text

C3900,IOS,ROUTER,c3900-universalk9-mz.SPA.156-3.M4.bin
C2900,IOS,ROUTER,c2900-universalk9-mz.SPA.156-3.M4.bin
ISR4300,IOS,ROUTER,isr4300-universalk9.16.03.06.SPA.bin

ios_upgrade.yaml: a ansible-playbook script text executable, ASCII text

#!/usr/local/bin/ansible-playbook -f 10
## Ansible playbook to handle IOS upgrades.
# Playbook will not reboot any device unless the variable REBOOT exists.
#
# Requires: https://github.com/networktocode/ntc-ansible
# Example: ansible-playbook --extra-vars "REBOOT=yes" ios_upgrade.yaml
# Example: ansible-playbook ios_upgrade.yaml --skip-tags=change
---
- name: Cisco IOS Upgrade
  hosts: [ "all" ]
  connection: local
  gather_facts: no
  tags: [ "IOS", "upgrade" ]

  vars_prompt:

  - name: "username"
    prompt: "Username"
    private: no

  - name: "password"
    prompt: "Password"

  vars:

  - ansible_connection: "local"
  - ansible_python_interpreter: "/usr/bin/env python"

  - ios_provider:
      username: "{{ username }}"
      password: "{{ password }}"
      authorize: true
      auth_pass: "{{ password }}"
      host: "{{ inventory_hostname }}"
      timeout: 120

  pre_tasks:

  - name: "ios_facts: hardware"
    ios_facts:
      gather_subset: hardware
      provider: "{{ ios_provider }}"
    connection: local
    when: (PLATFORM is not defined)
    tags: [ "pre_task", "ios_facts", "hardware" ]

  - name: "ios_command: boot configuration"
    ios_command:
      provider: "{{ ios_provider }}"
      commands:
        - "show running-config | include ^boot.system"
    connection: local
    register: COMMANDS
    tags: [ "pre_task", "ios_command", "boot", "COMMANDS" ]

  - name: "set_fact: PLATFORM"
    set_fact:
      PLATFORM: "{{ ansible_net_image|upper | regex_replace('.*[:/]') | regex_replace('([A-Z]-|-).*') }}"
    no_log: True
    when: (ansible_net_image is defined) and (PLATFORM is not defined)
    tags: [ "pre_task", "set_fact", "PLATFORM", "ansible_net_image" ]

  - name: "set_fact: SYSTEM"
    set_fact:
      SYSTEM: "{{ lookup('csvfile', PLATFORM + ' file=platform_facts.csv col=1 delimiter=,')|upper }}"
    no_log: True
    when: (PLATFORM is defined) and (SYSTEM is not defined)
    tags: [ "pre_task", "set_fact", "lookup", "platform_facts.csv", "PLATFORM", "SYSTEM" ]

  - name: "set_fact: TYPE"
    set_fact:
      TYPE: "{{ lookup('csvfile', PLATFORM + ' file=platform_facts.csv col=2 delimiter=,')|upper }}"
    no_log: True
    when: (PLATFORM is defined) and (TYPE is not defined)
    tags: [ "pre_task", "set_fact", "lookup", "platform_facts.csv", "PLATFORM", "TYPE" ]

  - name: "set_fact: IMAGE"
    set_fact:
      IMAGE: "{{ lookup('csvfile', PLATFORM + ' file=platform_facts.csv col=3 delimiter=,') }}"
    no_log: True
    when: (PLATFORM is defined) and (IMAGE is not defined)
    tags: [ "pre_task", "set_fact", "lookup", "platform_facts.csv", "PLATFORM", "IMAGE" ]

  - name: "stat: BACKUP_FILE"
    stat: path="backups/{{ inventory_hostname }}.cfg"
    no_log: True
    register: BACKUP_FILE
    tags: [ "pre_task", "stat", "BACKUP_FILE" ]

  - name: "stat: IMAGE_FILE"
    stat: path="images/{{ IMAGE }}"
    no_log: True
    register: IMAGE_FILE
    tags: [ "pre_task", "stat", "IMAGE_FILE" ]

  tasks:

  - name: "fail: missing image"
    fail:
      msg: "Platform image missing: {{ PLATFORM }}"
    when: (IMAGE[0] is undefined)

  - name: "ntc_save_config: host > local" 
    ntc_save_config:     
      platform: cisco_ios_ssh
      local_file: "backups/{{ inventory_hostname }}.cfg"
      provider: "{{ ios_provider }}"
    connection: local
    when: (BACKUP_FILE.stat.exists == False)
    tags: [ "ntc-ansible", "ntc_save_config", "cisco_ios_ssh", "BACKUP_FILE" ]

  - name: "ntc_file_copy: local > host"
    ntc_file_copy:
      platform: cisco_ios_ssh
      local_file: "images/{{ IMAGE }}"
      host: "{{ inventory_hostname }}"
      provider: "{{ ios_provider }}"
    connection: local
    when: (IMAGE_FILE.stat.exists == True) and (PLATFORM is defined) and (IMAGE is defined)
    tags: [ "ntc-ansible", "ntc_file_copy", "cisco_ios_ssh", "IMAGE", "PLATFORM", "IMAGE_FILE" ]

  - name: "ios_config: remove boot system lines"
    ios_config:
      provider: "{{ ios_provider }}"
      lines: "no {{ item }}"
    connection: local
    register: config_boot_rem
    with_items: "{{ COMMANDS.stdout_lines[0] }}"
    when: (PLATFORM is defined) and (IMAGE is defined) and
          not(IMAGE in item) and not(item == '')
    tags: [ "ios_config", "boot", "PLATFORM", "remove", "config_boot_rem", "change" ]
    notify:
      - ios write memory

  - name: "ios_config: add boot system line"
    ios_config:
      provider: "{{ ios_provider }}"
      lines: "boot system flash:{{ IMAGE }}"
      match: line
    connection: local
    register: config_boot_add
    when: (PLATFORM is defined) and (IMAGE is defined)
    tags: [ "ios_config", "boot", "PLATFORM", "IMAGE", "add", "config_boot_add", "change" ]
    notify:
      - ios write memory

  - meta: flush_handlers

  post_tasks:

  - name: "ntc_reboot: when REBOOT is defined"
    ntc_reboot:
      platform: cisco_ios_ssh
      confirm: true
      host: "{{ inventory_hostname }}"
      provider: "{{ ios_provider }}"
    connection: local
    when: (REBOOT is defined) and
          (config_boot_add.changed == true) or (config_boot_rem.changed == true)
    tags: [ "post_task", "ntc-ansible", "ntc_reboot", "REBOOT", "change" ]
    notify:
      - wait for tcp

  handlers:

  - name: "ios write memory"
    ios_command:
      provider: "{{ ios_provider }}"
      commands: "write memory"
    connection: local

  - name: "wait for tcp"
    wait_for:
      port: 22
      host: "{{inventory_hostname}}"
      timeout: 420
    connection: local

Comments closed