cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
472
Views
1
Helpful
0
Comments
Tagir Temirgaliyev
Spotlight
Spotlight

Part 1 was here https://community.cisco.com/t5/networking-knowledge-base/how-to-update-interface-description-for-cisco-switches-and-why/ta-p/5137184

In the Part 1 was explained very simple ansible script to update interface descriptions for Cisco routers and switches.

But I suppose not all people will use it in Prod because lack of visibility. Because we don't have full list of proposed changes. In some companies with strict rules, it will not be approved.

Better to have full list of changes before implementation.

So I created 2 python programs. One creates CSV file with Device_Name, Interface_name, Old_Description, New_Description.

And second actually adds interface description if we decided to do so after reading CSV file or editing it in Excel.

As inventory file I use ansible inventory file in YAML format and working with groups of devices. In this case "sw" means group of switches, "routers" means routers.

 

all:
  vars:
    ansible_user: admin
    ansible_password: admin
    ansible_connection: ansible.netcommon.network_cli
    ansible_network_os: ios
    ansible_become: yes
    ansible_become_method: enable
    ansible_become_password: cisco
    ansible_host_key_auto_add: yes
core_switch:
  hosts:
    core_switch1:
      ansible_host: 192.168.38.141
    core_switch2:
      ansible_host: 192.168.38.142
sw:
  hosts:
    access_switch3:
      ansible_host: 192.168.38.143
    access_switch4:
      ansible_host: 192.168.38.144
    access_switch5:
      ansible_host: 192.168.38.145
    access_switch6:
      ansible_host: 192.168.38.146
    access_switch7:
      ansible_host: 192.168.38.147
routers:
  hosts:
    R1:
      ansible_host: 192.168.38.151
    R2:
      ansible_host: 192.168.38.152

 

so first python:

 

#!/usr/bin/python3

import yaml
import argparse
from netmiko import ConnectHandler
import csv
import subprocess

# Function to parse command-line arguments
def parse_arguments():
    parser = argparse.ArgumentParser(description='Netmiko Script to Connect to Routers and Run Commands')
    parser.add_argument('--hosts_file', required=True, help='Path to the Ansible hosts file')
    parser.add_argument('--group', required=True, help='Group of routers to connect to from Ansible hosts file')
#    parser.add_argument('--command', required=True, help='Cisco command to run on the routers') # comm='sho cdp n | b D>
    return parser.parse_args()

def ping_ip(ip_address):   # Use ping command to check if it alive
    param = '-c' # for linux os
    # Build the command
    command = ['ping', param, '1', ip_address]
    try:
        # Execute the command
        subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True)
        return "yes"
    except subprocess.CalledProcessError:
        return "no"


# Main function
def main():
    # Parse command-line arguments
    args = parse_arguments()

    # Load the hosts file
    with open(args.hosts_file, 'r') as file:
        hosts_data = yaml.safe_load(file)

    # Extract global variables
    global_vars = hosts_data['all']['vars']

    # Extract router details for the specified group
    if args.group not in hosts_data:
        print(f"Group {args.group} not found in hosts file.")
        return
    routers = hosts_data[args.group]['hosts']

    comm1='sho int des | exc Interfac'
    comm2='sho cdp nei | beg Device'
    output_filed = args.group + '_inter_des.csv' #
    output_filec = args.group + '_inter_cdp.csv' #
    STRd = "Hostname,IP_address,Interface,State,Description"  #
    with open(output_filed, "w", newline="") as out_filed:
        writer = csv.writer(out_filed)
        out_filed.write(STRd)
        out_filed.write('\n')
    STRc = "Hostname,IP_address,Interface,New_Description"  # with ip
    with open(output_filec, "w", newline="") as out_filec:
        writer = csv.writer(out_filec)
        out_filec.write(STRc)
        out_filec.write('\n')
   # Connect to each router and execute the specified command
    for router_name, router_info in routers.items():
        if ping_ip(router_info['ansible_host']) == "no":   # check if host alive
            print( ' offline --------- ', router_name,'  ',router_info['ansible_host'])
            continue
        else:
            print( '  online --------- ', router_name,'  ',router_info['ansible_host'])
        # Create Netmiko connection dictionary
        netmiko_connection = {
            'device_type': 'cisco_ios',
            'host': router_info['ansible_host'],
            'username': global_vars['ansible_user'],
            'password': global_vars['ansible_password'],
            'secret': global_vars['ansible_become_password'],
        }

        # Establish SSH connection
        connection = ConnectHandler(**netmiko_connection)

        # Enter enable mode
        connection.enable()

        # Execute the specified command
        outputd1 = connection.send_command(comm1)
        outputd2 = connection.send_command(comm2)
        # Print the output
        print(f"  ------------ Output from {router_name} ({router_info['ansible_host']}):")
        print(f" ")
        lines = outputd1.strip().split('\n')
        for line in lines:
            rlin =  line[53:100]  # existing description
            sta =  line[46:52]    # interface up or down
            sta =  sta.replace(' ', '')   # remove all spaces]
            des = rlin.replace(' ', '')   # remove all spaces
            por=line[:30]         # port
            por=por.replace(' ', '')
            swi=router_name
            ipad= router_info['ansible_host']
            print("switch ",swi," port ",por, 'state ',sta," Descr ",des )
            STR = swi + "," + ipad + "," + por +"," + sta +"," + des # +","  # with ip
            with open(output_filed, 'a') as f:
                f.write(STR)
                f.write('\n')

        lines1 = outputd2.strip().split('\n')
        lines1 = lines1[1:]  # This correctly removes the first line (header)
        filtered_lines =  lines1
        try:
            first_empty_index = filtered_lines.index('')
            # Keep only the lines before the first empty line
            filtered_lines = filtered_lines[:first_empty_index]
        except ValueError:
            # No empty line found, do nothing
            pass

        lines1 = filtered_lines        # cleaned_text
        print(' filtered_lines ', filtered_lines)
        for line in lines1:
#            des=rlin =  line[:16] + '|' + line[58:67] + '|' + line[68:]
            rlin1 =  line[:16]
            dot_position = rlin1.find('.')
            rlin2 = rlin1[:dot_position]     # remove domain name from name
            rlin =  rlin2 + '|' + line[58:67] + '|' + line[68:]
            ndes = rlin.replace(' ', '')   # remove all spaces
            por=line[17:33]
            por1 = por[0:2]+por[3:33]   # remove 3rd char from port name
            por=por1.replace(' ', '')
            swi=router_name
            ipad= router_info['ansible_host']
            print("switch ",swi," port ",por, " Descr ", ndes )
            STRc = swi + "," + ipad + "," + por +"," + ndes  # with ip
            with open(output_filec, 'a') as f:
                f.write(STRc)
                f.write('\n')
        print(f"  ------------ end")
        connection.disconnect()    # Disconnect from device

    output_filem = args.group + '_merg.csv' #


    with open(output_filed, mode='r') as file:
        reader = csv.DictReader(file)
        sw_inter_des_data = list(reader)

# Read the sw_inter_cdp.csv file into a list of dictionaries
    with open(output_filec, mode='r') as file:
        reader = csv.DictReader(file)
        sw_inter_cdp_data = list(reader)

# Create a lookup dictionary for sw_inter_cdp_data based on Hostname, IP_address, and Interface
    cdp_lookup = {
        (row['Hostname'], row['IP_address'], row['Interface']): row['New_Description']
        for row in sw_inter_cdp_data
    }

# Add the New_Description to sw_inter_des_data
    for row in sw_inter_des_data:
        key = (row['Hostname'], row['IP_address'], row['Interface'])
        row['New_Description'] = cdp_lookup.get(key, '')

    # Write the updated data to a new CSV file
    with open(output_filem, mode='w', newline='') as file:
        fieldnames = sw_inter_des_data[0].keys()
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(sw_inter_des_data)

    print("New CSV file with added New_Description column has been created as ", args.group , '_merg.csv')



# Entry point of the script
if __name__ == '__main__':
    main()

 

after it we will have output CSV file as:

 

root@eve-ng:~/cdp# cat routers_merg.csv
Hostname,IP_address,Interface,State,Description,New_Description
R1,192.168.38.151,Fa0/0,up,R,R2|3725|Fas0/0
R1,192.168.38.151,Fa0/1,up,R,R2|3725|Fas0/1
R1,192.168.38.151,Fa1/0,up,R,Router|3725|Fas0/1
R2,192.168.38.152,Fa0/0,up,R1,R1|3725|Fas0/0
R2,192.168.38.152,Fa0/1,up,R1,R1|3725|Fas0/1
R2,192.168.38.152,Fa1/0,down,R1,
root@eve-ng:~/cdp#

 

and we can start second python to add New_Descriptions to interfaces:

 

#!/usr/bin/python3

import yaml
import argparse
import csv
import argparse
from netmiko import ConnectHandler

# Function to parse command-line arguments
def parse_arguments():
    parser = argparse.ArgumentParser(description='Netmiko Script to Connect to Routers and Run Commands')
    parser.add_argument('--hosts_file', required=True, help='Path to the Ansible hosts file')
    parser.add_argument('--group', required=True, help='Group of routers to connect to from Ansible hosts file')
#    parser.add_argument('--device', required=True, help='Device to connect to (e.g., R1 or R2)')
#    parser.add_argument('--commands', nargs='+', required=True, help='Cisco configuration commands to run')
    return parser.parse_args()

# Main function
def main():
    # Parse command-line arguments
    args = parse_arguments()

    # Load the hosts file
    with open(args.hosts_file, 'r') as file:
#    with open('hosts', 'r') as file:
        hosts_data = yaml.safe_load(file)

    # Extract global variables
    global_vars = hosts_data['all']['vars']

    # Extract device details
    file_path = args.group + '_merg.csv'       #'routers_merg.csv'
    # Create Netmiko connection dictionary
    with open(file_path, 'r') as csvfile:
        reader = csv.DictReader(csvfile)
        for row in reader:
            hostname = row['Hostname']
            if (row['New_Description'] =='' or row['New_Description']==row['Description']) :    # skip empty
                print(' skipping ', hostname, ' interface ', row['Interface'])
                continue

            dev_ip = row['IP_address']
            netmiko_connection = {
                'device_type': 'cisco_ios',
                'host': dev_ip,
                'username': global_vars['ansible_user'],
                'password': global_vars['ansible_password'],
                'secret': global_vars['ansible_become_password'],
                 }

            # Establish SSH connection
            connection = ConnectHandler(**netmiko_connection)

            # Enter enable mode
            connection.enable()

            # Execute the specified configuration commands
            to_implement_commands = [
                f"interface {row['Interface']}",
                f"description {row['New_Description']}"
            ]
            config_output = connection.send_config_set(to_implement_commands)
            print(' updating ', hostname, ' interface ', row['Interface'],' with descr ', row['New_Description'])
            # Save the configuration
            connection.save_config()
            # Disconnect
            connection.disconnect()

# Entry point of the script
if __name__ == '__main__':
    main()

 

 

The same way we can actually add almost any configuration from CSV file to different devices. For example configure different Vlan to different ports from CSV file.

Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community:

Review Cisco Networking for a $25 gift card