diff --git a/examples/apiData2CSV_v0.py b/examples/apiData2CSV_v0.py new file mode 100644 index 00000000..338040c3 --- /dev/null +++ b/examples/apiData2CSV_v0.py @@ -0,0 +1,116 @@ +import csv +from datetime import datetime +import os +import json +import argparse +import sys + +import meraki + +import urllib.parse +import platform + +# This example pulls API calls from the passed in org_id from the last timespan +# seconds, where the default timespan is 900 (hint 24 hours = 3600 seconds) and +# generates a CSV file with the data. +# +# Either input your API key below by uncommenting line 10 and changing line 16 to api_key=API_KEY, +# or set an environment variable (preferred) to define your API key. The former is insecure and not recommended. +# For example, in Linux/macOS: export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73 +# API_KEY = '093b24e85df15a3e66f1fc359f4c48493eaa1b73' +# +# Optionally, Cisco partners can set their BE GEO ID by using export BE_GEO_ID=XXXXXX +# where XXXXX is a valid BE GEO ID. This is used for metrics collection. +# +# Optionally, a calling application can be set by using export MERAKI_PYTHON_SDK_CALLER=YYYYY +# where YYYYY is a string identifying the application, script, or whatever piece of code +# is callig the Meraki Python SDK + + +def main(org_id, timespan): + # Instantiate a Meraki dashboard API session + dashboard = meraki.DashboardAPI( + base_url='https://api-mp.meraki.com/api/v0/', + print_console=False, + output_log=False, + ) + + # Get list of API usage data and start the output csv string + apiUsage = dashboard.api_usage.getOrganizationApiRequests(org_id, timespan=timespan, total_pages=-1) + csvString = 'method,host,path,queryString,tsDate,tsTime,responseCode,sourceIp,userAgent,' + csvString += 'implementation,implementationVersion,distro,distroVersion,system,systemRelease,' + csvString += 'cpu,be_geo_id,caller\r\n' + cumulativeAPIcalls = 0; + for use in apiUsage: + csvString += use['method'] + ',' + csvString += use['host'] + ',' + csvString += use['path'] + ',' + csvString += use['queryString'] + ',' + csvString += use['ts'].split('T')[0] + ',' + csvString += use['ts'].split('T')[1].replace('Z','') + ',' + csvString += str(use['responseCode']) + ',' + csvString += use['sourceIp'] + ',' + + # Special User-Agent processing + if 'python-meraki' in use['userAgent']: + print(use['userAgent']) + userAgent = use['userAgent'].split(' ') + csvString += userAgent[0] + ',' + if len(userAgent) > 1: + if "implementation" in userAgent[1]: + userAgentDict = json.loads(urllib.parse.unquote(userAgent[1])) + csvString += userAgentDict['implementation']['name'] + ',' + csvString += userAgentDict['implementation']['version'] + ',' + csvString += userAgentDict['distro']['name'] + ',' + csvString += userAgentDict['distro']['version'] + ',' + csvString += userAgentDict['system']['name'] + ',' + csvString += userAgentDict['system']['release'] + ',' + csvString += userAgentDict['cpu'] + ',' + if "be_geo_id" in userAgentDict: + csvString += userAgentDict['be_geo_id'] + ',' + else: + csvString += ',' + if "application" in userAgentDict: + csvString += userAgentDict['application'] + ',' + elif "caller" in userAgentDict: + csvString += userAgentDict['caller'] + ',' + else: + csvString += ',' + else: + csvString += ',,,,,,,,,' + else: + csvString += ',,,,,,,,,' + else: + csvString += use['userAgent']+ ',' + csvString += ',,,,,,,,,' + + csvString += '\r\n' + + # Output the file + now = datetime.now() + dt_string = now.strftime("%Y-%m-%d_%H-%M-%S") + filename = org_id + '_' + str(timespan) + '_' + dt_string + '.csv' + file = open(filename, 'w') + file.write(csvString) + file.close() + print('Results written to ' + filename) + +if __name__ == '__main__': + # First check for API key + if "MERAKI_DASHBOARD_API_KEY" not in os.environ: + print('You must set the MERAKI_DASHBOARD_API_KEY variable') + sys.exit() + + # Now check arguments + parser = argparse.ArgumentParser(description='Generate a CSV file of Meraki API activity for an organization.') + parser.add_argument('org_id', help='Organization id to pull API activity from') + parser.add_argument('--timespan', type=int, default=900, + help='The timespan (in seconds) for which the information will be fetched. Default = 900 (15 mins)') + args = parser.parse_args() + print('About to run with org_id: ' + args.org_id + ' and timespan: ' + str(args.timespan)) + + # Finally, let's roll + start_time = datetime.now() + main(args.org_id, args.timespan) + end_time = datetime.now() + print(f'\nScript complete, total runtime {end_time - start_time}') diff --git a/examples/apiData2CSV_v1.py b/examples/apiData2CSV_v1.py new file mode 100644 index 00000000..8bdedc94 --- /dev/null +++ b/examples/apiData2CSV_v1.py @@ -0,0 +1,116 @@ +import csv +from datetime import datetime +import os +import json +import argparse +import sys + +import meraki + +import urllib.parse +import platform + +# This example pulls API calls from the passed in org_id from the last timespan +# seconds, where the default timespan is 900 (hint 24 hours = 3600 seconds) and +# generates a CSV file with the data. +# +# Either input your API key below by uncommenting line 10 and changing line 16 to api_key=API_KEY, +# or set an environment variable (preferred) to define your API key. The former is insecure and not recommended. +# For example, in Linux/macOS: export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73 +# API_KEY = '093b24e85df15a3e66f1fc359f4c48493eaa1b73' +# +# Optionally, Cisco partners can set their BE GEO ID by using export BE_GEO_ID=XXXXXX +# where XXXXX is a valid BE GEO ID. This is used for metrics collection. +# +# Optionally, a calling application can be set by using export MERAKI_PYTHON_SDK_CALLER=YYYYY +# where YYYYY is a string identifying the application, script, or whatever piece of code +# is callig the Meraki Python SDK + + +def main(org_id, timespan): + # Instantiate a Meraki dashboard API session + dashboard = meraki.DashboardAPI( + base_url='https://api-mp.meraki.com/api/v1/', + print_console=False, + output_log=False, + ) + + # Get list of API usage data and start the output csv string + apiUsage = dashboard.organizations.getOrganizationApiRequests(org_id, timespan=timespan, total_pages=-1) + csvString = 'method,host,path,queryString,tsDate,tsTime,responseCode,sourceIp,userAgent,' + csvString += 'implementation,implementationVersion,distro,distroVersion,system,systemRelease,' + csvString += 'cpu,be_geo_id,caller\r\n' + cumulativeAPIcalls = 0; + for use in apiUsage: + csvString += use['method'] + ',' + csvString += use['host'] + ',' + csvString += use['path'] + ',' + csvString += use['queryString'] + ',' + csvString += use['ts'].split('T')[0] + ',' + csvString += use['ts'].split('T')[1].replace('Z','') + ',' + csvString += str(use['responseCode']) + ',' + csvString += use['sourceIp'] + ',' + + # Special User-Agent processing + if 'python-meraki' in use['userAgent']: + print(use['userAgent']) + userAgent = use['userAgent'].split(' ') + csvString += userAgent[0] + ',' + if len(userAgent) > 1: + if "implementation" in userAgent[1]: + userAgentDict = json.loads(urllib.parse.unquote(userAgent[1])) + csvString += userAgentDict['implementation']['name'] + ',' + csvString += userAgentDict['implementation']['version'] + ',' + csvString += userAgentDict['distro']['name'] + ',' + csvString += userAgentDict['distro']['version'] + ',' + csvString += userAgentDict['system']['name'] + ',' + csvString += userAgentDict['system']['release'] + ',' + csvString += userAgentDict['cpu'] + ',' + if "be_geo_id" in userAgentDict: + csvString += userAgentDict['be_geo_id'] + ',' + else: + csvString += ',' + if "application" in userAgentDict: + csvString += userAgentDict['application'] + ',' + elif "caller" in userAgentDict: + csvString += userAgentDict['caller'] + ',' + else: + csvString += ',' + else: + csvString += ',,,,,,,,,' + else: + csvString += ',,,,,,,,,' + else: + csvString += use['userAgent']+ ',' + csvString += ',,,,,,,,,' + + csvString += '\r\n' + + # Output the file + now = datetime.now() + dt_string = now.strftime("%Y-%m-%d_%H-%M-%S") + filename = org_id + '_' + str(timespan) + '_' + dt_string + '.csv' + file = open(filename, 'w') + file.write(csvString) + file.close() + print('Results written to ' + filename) + +if __name__ == '__main__': + # First check for API key + if "MERAKI_DASHBOARD_API_KEY" not in os.environ: + print('You must set the MERAKI_DASHBOARD_API_KEY variable') + sys.exit() + + # Now check arguments + parser = argparse.ArgumentParser(description='Generate a CSV file of Meraki API activity for an organization.') + parser.add_argument('org_id', help='Organization id to pull API activity from') + parser.add_argument('--timespan', type=int, default=900, + help='The timespan (in seconds) for which the information will be fetched. Default = 900 (15 mins)') + args = parser.parse_args() + print('About to run with org_id: ' + args.org_id + ' and timespan: ' + str(args.timespan)) + + # Finally, let's roll + start_time = datetime.now() + main(args.org_id, args.timespan) + end_time = datetime.now() + print(f'\nScript complete, total runtime {end_time - start_time}') diff --git a/meraki_v0/__init__.py b/meraki_v0/__init__.py index 889a4cf6..9b4b99fd 100644 --- a/meraki_v0/__init__.py +++ b/meraki_v0/__init__.py @@ -108,6 +108,8 @@ class DashboardAPI(object): - print_console (boolean): print logging output to console? - suppress_logging (boolean): disable all logging? you're on your own then! - simulate (boolean): simulate POST/PUT/DELETE calls to prevent changes? + - be_geo_id (string): optional partner identifier for API usage tracking; can also be set as an environment variable BE_GEO_ID + - caller (string): optional identifier for API usage tracking; can also be set as an environment variable MERAKI_PYTHON_SDK_CALLER """ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeout=SINGLE_REQUEST_TIMEOUT, @@ -117,12 +119,18 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo retry_4xx_error=RETRY_4XX_ERROR, retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME, maximum_retries=MAXIMUM_RETRIES, output_log=OUTPUT_LOG, log_path=LOG_PATH, log_file_prefix=LOG_FILE_PREFIX, print_console=PRINT_TO_CONSOLE, suppress_logging=SUPPRESS_LOGGING, - simulate=SIMULATE_API_CALLS): + simulate=SIMULATE_API_CALLS, be_geo_id='', caller=''): # Check API key api_key = api_key or os.environ.get(API_KEY_ENVIRONMENT_VARIABLE) if not api_key: raise APIKeyError() + # Pull the BE GEO ID from an environment variable if present + be_geo_id = be_geo_id or os.environ.get('BE_GEO_ID') + + # Pull the caller from an environment variable if present + caller = caller or os.environ.get('MERAKI_PYTHON_SDK_CALLER') + # Configure logging if not suppress_logging: self._logger = logging.getLogger(__name__) @@ -163,6 +171,8 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo retry_4xx_error_wait_time=retry_4xx_error_wait_time, maximum_retries=maximum_retries, simulate=simulate, + be_geo_id=be_geo_id, + caller=caller, ) # API endpoints by section diff --git a/meraki_v0/rest_session.py b/meraki_v0/rest_session.py index 6b4c04c7..c9a84de4 100644 --- a/meraki_v0/rest_session.py +++ b/meraki_v0/rest_session.py @@ -4,9 +4,52 @@ import requests +import urllib.parse +import platform + from .config import * from .exceptions import * +def user_agent_extended(be_geo_id, caller): + # Generate extended portion of the User-Agent + user_agent_extended = be_geo_id + user_agent_extended = {} + + # Mimic pip system data collection per https://github.com/pypa/pip/blob/master/src/pip/_internal/network/session.py + user_agent_extended['implementation'] = { + "name": platform.python_implementation(), + } + + if user_agent_extended["implementation"]["name"] in ('CPython','Jython','IronPython'): + user_agent_extended["implementation"]["version"] = platform.python_version() + elif user_agent_extended["implementation"]["name"] == 'PyPy': + if sys.pypy_version_info.releaselevel == 'final': + pypy_version_info = sys.pypy_version_info[:3] + else: + pypy_version_info = sys.pypy_version_info + user_agent_extended["implementation"]["version"] = ".".join( + [str(x) for x in pypy_version_info] + ) + + if sys.platform.startswith("darwin") and platform.mac_ver()[0]: + user_agent_extended["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]} + + if platform.system(): + user_agent_extended.setdefault("system", {})["name"] = platform.system() + + if platform.release(): + user_agent_extended.setdefault("system", {})["release"] = platform.release() + + if platform.machine(): + user_agent_extended["cpu"] = platform.machine() + + if be_geo_id: + user_agent_extended["be_geo_id"] = be_geo_id + + if caller: + user_agent_extended["caller"] = caller + + return urllib.parse.quote(json.dumps(user_agent_extended)) # Main module interface class RestSession(object): @@ -24,6 +67,8 @@ def __init__( retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME, maximum_retries=MAXIMUM_RETRIES, simulate=SIMULATE_API_CALLS, + be_geo_id='', + caller='' ): super(RestSession, self).__init__() @@ -39,6 +84,8 @@ def __init__( self._retry_4xx_error_wait_time = retry_4xx_error_wait_time self._maximum_retries = maximum_retries self._simulate = simulate + self._be_geo_id = be_geo_id + self._caller = caller # Initialize a new `requests` session self._req_session = requests.session() @@ -54,7 +101,7 @@ def __init__( self._req_session.headers = { 'X-Cisco-Meraki-API-Key': self._api_key, 'Content-Type': 'application/json', - 'User-Agent': 'python-meraki/0.100.2', + 'User-Agent': 'python-meraki/0.100.2' + user_agent_extended(be_geo_id, caller), } # Log API calls diff --git a/meraki_v1/__init__.py b/meraki_v1/__init__.py index b9b29862..978f250a 100644 --- a/meraki_v1/__init__.py +++ b/meraki_v1/__init__.py @@ -42,6 +42,8 @@ class DashboardAPI(object): - print_console (boolean): print logging output to console? - suppress_logging (boolean): disable all logging? you're on your own then! - simulate (boolean): simulate POST/PUT/DELETE calls to prevent changes? + - be_geo_id (string): optional partner identifier for API usage tracking; can also be set as an environment variable BE_GEO_ID + - caller (string): optional identifier for API usage tracking; can also be set as an environment variable MERAKI_PYTHON_SDK_CALLER """ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeout=SINGLE_REQUEST_TIMEOUT, @@ -51,12 +53,18 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo retry_4xx_error=RETRY_4XX_ERROR, retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME, maximum_retries=MAXIMUM_RETRIES, output_log=OUTPUT_LOG, log_path=LOG_PATH, log_file_prefix=LOG_FILE_PREFIX, print_console=PRINT_TO_CONSOLE, suppress_logging=SUPPRESS_LOGGING, - simulate=SIMULATE_API_CALLS): + simulate=SIMULATE_API_CALLS, be_geo_id='', caller=''): # Check API key api_key = api_key or os.environ.get(API_KEY_ENVIRONMENT_VARIABLE) if not api_key: raise APIKeyError() + # Pull the BE GEO ID from an environment variable if present + be_geo_id = be_geo_id or os.environ.get('BE_GEO_ID') + + # Pull the caller from an environment variable if present + caller = caller or os.environ.get('MERAKI_PYTHON_SDK_CALLER') + # Configure logging if not suppress_logging: self._logger = logging.getLogger(__name__) @@ -97,6 +105,8 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo retry_4xx_error_wait_time=retry_4xx_error_wait_time, maximum_retries=maximum_retries, simulate=simulate, + be_geo_id=be_geo_id, + caller=caller, ) # API endpoints by section diff --git a/meraki_v1/rest_session.py b/meraki_v1/rest_session.py index e70b2deb..0bd2a4c8 100644 --- a/meraki_v1/rest_session.py +++ b/meraki_v1/rest_session.py @@ -4,9 +4,52 @@ import requests +import urllib.parse +import platform + from .config import * from .exceptions import * +def user_agent_extended(be_geo_id, caller): + # Generate extended portion of the User-Agent + user_agent_extended = be_geo_id + user_agent_extended = {} + + # Mimic pip system data collection per https://github.com/pypa/pip/blob/master/src/pip/_internal/network/session.py + user_agent_extended['implementation'] = { + "name": platform.python_implementation(), + } + + if user_agent_extended["implementation"]["name"] in ('CPython','Jython','IronPython'): + user_agent_extended["implementation"]["version"] = platform.python_version() + elif user_agent_extended["implementation"]["name"] == 'PyPy': + if sys.pypy_version_info.releaselevel == 'final': + pypy_version_info = sys.pypy_version_info[:3] + else: + pypy_version_info = sys.pypy_version_info + user_agent_extended["implementation"]["version"] = ".".join( + [str(x) for x in pypy_version_info] + ) + + if sys.platform.startswith("darwin") and platform.mac_ver()[0]: + user_agent_extended["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]} + + if platform.system(): + user_agent_extended.setdefault("system", {})["name"] = platform.system() + + if platform.release(): + user_agent_extended.setdefault("system", {})["release"] = platform.release() + + if platform.machine(): + user_agent_extended["cpu"] = platform.machine() + + if be_geo_id: + user_agent_extended["be_geo_id"] = be_geo_id + + if caller: + user_agent_extended["caller"] = caller + + return urllib.parse.quote(json.dumps(user_agent_extended)) # Main module interface class RestSession(object): @@ -24,6 +67,8 @@ def __init__( retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME, maximum_retries=MAXIMUM_RETRIES, simulate=SIMULATE_API_CALLS, + be_geo_id='', + caller='' ): super(RestSession, self).__init__() @@ -39,6 +84,8 @@ def __init__( self._retry_4xx_error_wait_time = retry_4xx_error_wait_time self._maximum_retries = maximum_retries self._simulate = simulate + self._be_geo_id = be_geo_id + self._caller = caller # Initialize a new `requests` session self._req_session = requests.session() @@ -54,7 +101,7 @@ def __init__( self._req_session.headers = { 'Authorization': 'Bearer ' + self._api_key, 'Content-Type': 'application/json', - 'User-Agent': 'python-meraki/1.0.0b1', + 'User-Agent': 'python-meraki/1.0.0b1 ' + user_agent_extended(be_geo_id, caller), } # Log API calls