Below is what I came up with a while ago. It will output an excel file with all the DHCP reservations for all networks for all orgs that the API key can access.
Note API Key is retrieved from an Environment variable.
import csv
from datetime import datetime
import os
import time
import meraki
import pandas as pd
import json
import xlsxwriter
import openpyxl
from collections import OrderedDict
import time
# This script get a list of all VLANs for All Meraki Networks in All Meraki Orgs the API Key has access to
#This also grab single subnet networks and skip any networks that has a passthrough appliances as they do not have a subnet.
#It then get all the DHCP reservations from each VLANs.
# Meraki API Key name. Allows to switch between different keys
MERAKI_DASHBOARD_API_KEY_NAME = 'MERAKI_API_KEY GOES HERE' # Readonly key
#Set CSV / XLXS folder path
output_path = f"{os.path.dirname(__file__)}/output"
def main():
if MERAKI_DASHBOARD_API_KEY_NAME in os.environ:
print(f"API Key Environment variable [{MERAKI_DASHBOARD_API_KEY_NAME}] found.")
else:
print(f"API Key Environment variable [{MERAKI_DASHBOARD_API_KEY_NAME}] Does not exists!")
exit(1)
MERAKI_DASHBOARD_API_KEY = os.getenv(MERAKI_DASHBOARD_API_KEY_NAME) # Get the actual Meraki API Key
# Start a Meraki dashboard API session
try:
print(f"\nTrying to connect to Meraki API Using [{MERAKI_DASHBOARD_API_KEY_NAME}]")
dashboard = meraki.DashboardAPI(
api_key=MERAKI_DASHBOARD_API_KEY,
base_url='https://api.meraki.com/api/v1/',
print_console=False, output_log=False, suppress_logging=True,
wait_on_rate_limit=True,
maximum_retries=100
)
except meraki.APIError as e:
print("Unable to connect to Meraki API.")
print(f'Meraki API error: {e}')
print(f'status code = {e.status}')
print(f'reason = {e.reason}')
print(f'error = {e.message}')
else:
# Get list of organizations to which the supplied API key has access to.
organizations = dashboard.organizations.getOrganizations()
organizations = sorted(organizations, key=lambda x: x['name']) # Sort Org by name
# Print name of the Orgs found
print(f"Found: [{len(organizations)}] organisations.")
for org in organizations:
print(f"{org['name']}")
all_orgs_networks_vlans = list() #Store a list of all the org vlans
all_orgs_networks_vlans_dhcp_reservations = list() # Store a list of all the org vlans reservations
# Iterate through list of Orgs and gather clients details of all VLAN for each networks where there is an MX
for org in organizations:
all_org_networks = [] # Store all the networks in all orgs so we can write to a file later.
print(f"\nAnalysing organization {org['name']}:")
org_id = org['id'] # Save ID name for use later
org_name = org['name'] # Save Org name for use later
# Get list of networks in current organization
try:
org_networks = dashboard.organizations.getOrganizationNetworks(org_id)
except meraki.APIError as e:
print(f'Meraki API error: {e}')
print(f'status code = {e.status}')
print(f'reason = {e.reason}')
print(f'error = {e.message}')
continue
except Exception as e:
print(f'some other error: {e}')
continue # Skip error in current Mersaki network
# Sort List of network by name
org_networks = sorted(org_networks, key=lambda x: x['name'])
# Iterate through networks in current organisation
total = len(org_networks) # count number of Meraki networks in this org
counter = 1 # Keep count of number of network as we iterate through them.
print(f'Iterating through {total} networks in organization in {org["name"]} to get list of VLAN information')
# Create a new list networks
current_org_networks_info = []
# Loop through the list of networks and retrieve the VLAN info
for network in org_networks:
print(f'Processing [{org["name"]}] Network: {network["name"]}')
#We can check if a network has vlans by using below endpoint
# https://developer.cisco.com/meraki/api/get-network-appliance-vlans-settings/
#
#However to reduce the number of API calls. This will be skipped
# Create a new dictionary to store the network info
network_info = {}
# Add the required fields to the new dictionary
network_info["id"] = network["id"]
network_info["organizationId"] = network["organizationId"]
network_info["name"] = network["name"]
network_info["productTypes"] = network["productTypes"]
network_info["timeZone"] = network["timeZone"]
network_info["tags"] = network["tags"]
network_info["isBoundToConfigTemplate"] = network["isBoundToConfigTemplate"]
network_info["url"] = network["url"]
network_vlans = list() #Use to hold all the vlan or empty if no vlans
vlan_details = {} #Store the vlan details for each vlan
#If product type has "appliance" then it will have a vlan subnet for us to grab
if 'appliance' in network['productTypes']:
#Get VLAN Info
try:
vlans = dashboard.appliance.getNetworkApplianceVlans(network["id"])
#vlans now has the VLAN details including any DHCP reservations
#Now loop through all the VLAn and get the DHCP Reservations clients MAC & IP
for vlan in vlans:
fixedIpAssignments = vlan['fixedIpAssignments']
for key in fixedIpAssignments:
# vlan_details['organizationId'] = network['organizationId']
# vlan_details['net_name'] = network['name']
# vlan_details["vlan_name"] = vlan['name']
# vlan_details['dnsNameservers'] = vlan['dnsNameservers']
# vlan_details['reservation_name'] = fixedIpAssignments[f"{key}"]['name']
# vlan_details['reservation_mac'] = f"{key}"
vlan_details['reservation_ip'] = fixedIpAssignments[f"{key}"]['ip']
all_orgs_networks_vlans_dhcp_reservations.append(
{'organizationId': network['organizationId'],
'net_name': network['name'],
'vlan_name': vlan['name'],
'dnsNameservers': vlan['dnsNameservers'],
'reservation_name': fixedIpAssignments[f"{key}"]['name'],
'reservation_mac': f"{key}",
'reservation_ip': fixedIpAssignments[f"{key}"]['ip']
}
)
except meraki.APIError as e:
if e.status == 400: #Vlan not enable for this network as only a single subnet
#Get the single VLAN subnet and assume VLAN ID of '1'
#print(f'Error Network: {network["name"]}: {e.message}')
#Check and skip any network & appliances in passthrough mode.
#They won't have any subnet to retrieve
# if 'ACM-VNET-MEL-APAS-ACM-01' in network['name']:
# print('Pass through mode network')
#Get the single VLAN
try:
single_vlan_info = dashboard.appliance.getNetworkApplianceSingleLan(network["id"])
fixedIpAssignments = single_vlan_info['fixedIpAssignments']
for key in fixedIpAssignments:
vlan_details['reservation_ip'] = fixedIpAssignments[f"{key}"]['ip']
all_orgs_networks_vlans_dhcp_reservations.append(
{'organizationId': network['organizationId'],
'net_name': network['name'],
'vlan_name': 'Default',
'dnsNameservers': single_vlan_info['dnsCustomNameservers'],
'reservation_name': fixedIpAssignments[f"{key}"]['name'],
'reservation_mac': f"{key}",
'reservation_ip': fixedIpAssignments[f"{key}"]['ip']
}
)
# print(f"Done")
except meraki.APIError as e2:
if e2.status == 400:
print(f"Network in Passthrough mode or some other error. Skipping!")
#'Single LAN is not available for networks in passthrough mode'
continue
else:
print(f'Some other error for network without VLANs.')
continue
#If we are here then there is a single subnet to grab
vlan_subnet = {1, single_vlan_info['subnet']}
print(f"VLAN [1]: - Subnet [{single_vlan_info['subnet']}]")
network_vlans.append(vlan_subnet)
# Also store the VLAN in the All Org VLAN list for later usage
all_orgs_networks_vlans.append({'net_name': network["name"], 'net_id': network["id"],
'vlan_id': 1, 'vlan_subnet': single_vlan_info['subnet']})
continue
elif e.status == 429:
#We have hit the rate limit. Back off by sleeping for a bit
print(f'Error Network: {network["name"]}: {e.message}. Sleeping for 5 seconds.')
#time.sleep(int(response.headers["Retry-After"]))
time.sleep(5) # Sleep for 5 seconds
continue
else:
print(f'Error Network: {network["name"]}')
print(f'Meraki API error: {e}')
print(f'status code = {e.status}')
print(f'reason = {e.reason}')
print(f'error = {e.message}')
continue
#If we are here then there are some vlan subnets to process
# Loop through the appliance vlan info and get all VLANs subnets
for vlan in vlans:
# print(f"VLAN ID: [{$vlan['id']}] Subnet: {$vlan['subnet']} ")
vlan_subnet = {vlan['id'], vlan['subnet']}
print(f"VLAN [{vlan['id']}]: - Subnet [{vlan['subnet']}]")
network_vlans.append(vlan_subnet)
#Also store the VLAN in the All Org VLAN list for later usage
all_orgs_networks_vlans.append({'net_name': network["name"], 'net_id': network["id"],
'vlan_id': vlan['id'], 'vlan_subnet': vlan['subnet']})
#store the network vlans (or empty if not an appliance network
network_info['vlans'] = network_vlans
# Add the new dictionary to the list
current_org_networks_info.append(network_info)
print(f"Network info gathering for [{org['name'] }] completed.")
#Combine the curen org network info into the all_org dict for later use
all_org_networks = current_org_networks_info + all_org_networks
#Sort the networks by name
#all_orgs_networks_vlans
df_all_orgs_networks_vlans = pd.DataFrame(all_orgs_networks_vlans)
df_all_orgs_networks_vlans_dhcp_reservations = pd.DataFrame(all_orgs_networks_vlans_dhcp_reservations)
#df_all_org_networks_info = pd.DataFrame(all_org_networks)
#
# write the DataFrame to a CSV file
current_datetime = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
filename = f"{output_path}/all-orgs_networks-vlan-info--{current_datetime}.xlsx"
#df_all_org_networks_info.to_excel(filename)
df_all_orgs_networks_vlans.to_excel(filename)
print(f"Exported[VLANS to file] filename: {filename}")
#Write DHCP reservation to file
current_datetime = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
filename = f"{output_path}/all-orgs_networks-dhcp-reservation-info--{current_datetime}.xlsx"
#df_all_org_networks_info.to_excel(filename)
df_all_orgs_networks_vlans_dhcp_reservations.to_excel(filename)
print(f"Exported [DHCP reservation to file] filename: {filename}")
if __name__ == "__main__":
# Call Main to do all the actual work.
start_time = datetime.now()
main()
end_time = datetime.now()
print(f'\nScript complete, total runtime {end_time - start_time}')