# This is a script to send an email alert if the remaining license time in an org an admin has access to is # less than X days, or if its license capacity is not enough for its current device count. The alert is # sent using an SMTP server; by default Gmail. Use an automation platform like Zapier to read this email # and trigger further actions. # # To run the script, enter: # python merakilicensealert.py -k [-u -p -d ] [-s ] [-t ] [-m include_empty] # # Mandatory argument: # -k : Your Meraki Dashboard API key # Arguments to enable sending emails. All three must be given to send email: # -u : The username (email address) that will be used to send the alert message # -p : Password for the email address where the message is sent from # -d : Recipient email address # Optional arguments: # -s : Server to use for sending SMTP. If omitted, Gmail will be used # -t : Alert threshold in days for generating alert. Default is 90 # -m include_empty : Flag: Also send warnings for new orgs with no devices # # Example 1, send email for orgs with 180 or less days license remaining: # python merakilicensealert.py -k 1234 -u sourceaccount@gmail.com -p 4321 -d alerts@myserver.com -t 180 # Example 2, print orgs with 360 or less days remaining to screen: # python merakilicensealert.py -k 1234 -t 360 # # To make script chaining easier, all lines containing informational messages to the user # start with the character @ # # This file was last modified on 2018-02-22 import sys, getopt, requests, json, time, smtplib from datetime import datetime, date class c_organizationdata: def __init__(self): self.name = '' self.id = '' self.shardhost = '' self.licensestate = '' self.timeremaining = 0 #end class #Used for time.sleep(API_EXEC_DELAY). Delay added to avoid hitting dashboard API max request rate API_EXEC_DELAY = 0.21 #connect and read timeouts for the Requests module REQUESTS_CONNECT_TIMEOUT = 30 REQUESTS_READ_TIMEOUT = 30 #used by merakirequestthrottler(). DO NOT MODIFY LAST_MERAKI_REQUEST = datetime.now() def printusertext(p_message): #prints a line of text that is meant for the user to read #do not process these lines when chaining scripts print('@ %s' % p_message) def printhelp(): #prints help text printusertext('This is a script to send an email alert if the remaining license time in an org an admin has access to is') printusertext(' less than X days, or if its license capacity is not enough for its current device count. The alert is') printusertext(' sent using an SMTP server; by default Gmail. Use an automation platform like Zapier to read this email') printusertext(' and trigger further actions.') printusertext('') printusertext('To run the script, enter:') printusertext(' python merakilicensealert.py -k [-u -p -d ] [-s ] [-t ] [-m include_empty]') printusertext('') printusertext('Mandatory argument:') printusertext(' -k : Your Meraki Dashboard API key') printusertext('Arguments to enable sending emails. All three must be given to send email:') printusertext(' -u : The username (email address) that will be used to send the alert message') printusertext(' -p : Password for the email address where the message is sent from') printusertext(' -d : Recipient email address') printusertext('Optional arguments:') printusertext(' -s : Server to use for sending SMTP. If omitted, Gmail will be used') printusertext(' -t : Alert threshold in days for generating alert. Default is 90') printusertext(' -m include_empty : Flag: Also send warnings for new orgs with no devices ') printusertext('') printusertext('Example 1, send email for orgs with 180 or less days license remaining:') printusertext(' python merakilicensealert.py -k 1234 -u sourceaccount@gmail.com -p 4321 -d alerts@myserver.com -t 180') printusertext('Example 2, print orgs with 360 or less days remaining to screen:') printusertext(' python merakilicensealert.py -k 1234 -t 360') printusertext('') printusertext('Use double quotes ("") in Windows to pass arguments containing spaces. Names are case-sensitive.') def merakirequestthrottler(p_requestcount=1): #makes sure there is enough time between API requests to Dashboard not to hit shaper global LAST_MERAKI_REQUEST if (datetime.now()-LAST_MERAKI_REQUEST).total_seconds() < (API_EXEC_DELAY*p_requestcount): time.sleep(API_EXEC_DELAY*p_requestcount) LAST_MERAKI_REQUEST = datetime.now() return def getorglist(p_apikey): #returns the organizations' list for a specified admin merakirequestthrottler() try: r = requests.get('https://dashboard.meraki.com/api/v0/organizations', headers={'X-Cisco-Meraki-API-Key': p_apikey, 'Content-Type': 'application/json'}, timeout=(REQUESTS_CONNECT_TIMEOUT, REQUESTS_READ_TIMEOUT)) except: printusertext('ERROR 01: Unable to contact Meraki cloud') sys.exit(2) returnvalue = [] if r.status_code != requests.codes.ok: returnvalue.append({'id':'null'}) return returnvalue rjson = r.json() return(rjson) def getshardhost(p_apikey, p_orgid): #Looks up shard URL for a specific org. Use this URL instead of 'dashboard.meraki.com' # when making API calls with API accounts that can access multiple orgs. #On failure returns 'null' merakirequestthrottler() try: r = requests.get('https://dashboard.meraki.com/api/v0/organizations/%s/snmp' % p_orgid, headers={'X-Cisco-Meraki-API-Key': p_apikey, 'Content-Type': 'application/json'}, timeout=(REQUESTS_CONNECT_TIMEOUT, REQUESTS_READ_TIMEOUT)) except: printusertext('ERROR 02: Unable to contact Meraki cloud') sys.exit(2) if r.status_code != requests.codes.ok: return 'null' rjson = r.json() return(rjson['hostname']) def getlicensestate(p_apikey, p_shardhost, p_orgid): #returns the organizations' list for a specified admin merakirequestthrottler() try: r = requests.get('https://%s/api/v0/organizations/%s/licenseState' % (p_shardhost, p_orgid) , headers={'X-Cisco-Meraki-API-Key': p_apikey, 'Content-Type': 'application/json'}, timeout=(REQUESTS_CONNECT_TIMEOUT, REQUESTS_READ_TIMEOUT)) except: printusertext('ERROR 03: Unable to contact Meraki cloud') sys.exit(2) returnvalue = [] if r.status_code != requests.codes.ok: returnvalue.append({'status':'null'}) return returnvalue rjson = r.json() return(rjson) def calcdaysremaining(p_merakidate): #calculates how many days remain between today and a date expressed in the Dashboard API license time format mdate = datetime.date(datetime.strptime(p_merakidate, '%b %d, %Y UTC')) today = date.today() #the first part (before space) of the date difference is number of days. rest is garbage retvalue = int(str(mdate - today).split(' ')[0]) return retvalue def checklicensewarning(p_apikey, p_orglist, p_timethreshold, p_modeincludeempty = False): #checks org list for license violations and expiration warnings filterlist = [] i = 0 for org in p_orglist: licensestate = getlicensestate(p_apikey, org.shardhost, org.id) if licensestate['expirationDate'] == 'N/A': if p_modeincludeempty: timeremaining = 0 else: if licensestate['status'] != 'License Required': timeremaining = p_timethreshold + 1 else: timeremaining = 0 else: timeremaining = calcdaysremaining(licensestate['expirationDate']) if licensestate['status'] != 'OK' or timeremaining <= p_timethreshold: if licensestate['status'] != 'N/A' or p_modeincludeempty: filterlist.append(c_organizationdata()) filterlist[i].id = org.id filterlist[i].name = org.name filterlist[i].shardhost = org.shardhost filterlist[i].licensestate = licensestate['status'] filterlist[i].timeremaining = timeremaining i += 1 return(filterlist) def main(argv): #python merakilicensealert.py -k -u -p -d [-t