on 07-11-2024 06:17 AM
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.
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: