Skip to content

Commit a2d3462

Browse files
committed
Solves meraki#86 for v1, extended User-Agent data
1 parent 088d372 commit a2d3462

3 files changed

Lines changed: 177 additions & 2 deletions

File tree

examples/apiData2CSV_v1.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import csv
2+
from datetime import datetime
3+
import os
4+
import json
5+
import argparse
6+
import sys
7+
8+
import meraki
9+
10+
import urllib.parse
11+
import platform
12+
13+
# This example pulls API calls from the passed in org_id from the last timespan
14+
# seconds, where the default timespan is 900 (hint 24 hours = 3600 seconds) and
15+
# generates a CSV file with the data.
16+
#
17+
# Either input your API key below by uncommenting line 10 and changing line 16 to api_key=API_KEY,
18+
# or set an environment variable (preferred) to define your API key. The former is insecure and not recommended.
19+
# For example, in Linux/macOS: export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73
20+
# API_KEY = '093b24e85df15a3e66f1fc359f4c48493eaa1b73'
21+
#
22+
# Optionally, Cisco partners can set their BE GEO ID by using export BE_GEO_ID=XXXXXX
23+
# where XXXXX is a valid BE GEO ID. This is used for metrics collection.
24+
#
25+
# Optionally, a calling application can be set by using export MERAKI_PYTHON_SDK_CALLER=YYYYY
26+
# where YYYYY is a string identifying the application, script, or whatever piece of code
27+
# is callig the Meraki Python SDK
28+
29+
30+
def main(org_id, timespan):
31+
# Instantiate a Meraki dashboard API session
32+
dashboard = meraki.DashboardAPI(
33+
base_url='https://api-mp.meraki.com/api/v1/',
34+
print_console=False,
35+
output_log=False,
36+
)
37+
38+
# Get list of API usage data and start the output csv string
39+
apiUsage = dashboard.organizations.getOrganizationApiRequests(org_id, timespan=timespan, total_pages=-1)
40+
csvString = 'method,host,path,queryString,tsDate,tsTime,responseCode,sourceIp,userAgent,'
41+
csvString += 'implementation,implementationVersion,distro,distroVersion,system,systemRelease,'
42+
csvString += 'cpu,be_geo_id,caller\r\n'
43+
cumulativeAPIcalls = 0;
44+
for use in apiUsage:
45+
csvString += use['method'] + ','
46+
csvString += use['host'] + ','
47+
csvString += use['path'] + ','
48+
csvString += use['queryString'] + ','
49+
csvString += use['ts'].split('T')[0] + ','
50+
csvString += use['ts'].split('T')[1].replace('Z','') + ','
51+
csvString += str(use['responseCode']) + ','
52+
csvString += use['sourceIp'] + ','
53+
54+
# Special User-Agent processing
55+
if 'python-meraki' in use['userAgent']:
56+
print(use['userAgent'])
57+
userAgent = use['userAgent'].split(' ')
58+
csvString += userAgent[0] + ','
59+
if len(userAgent) > 1:
60+
if "implementation" in userAgent[1]:
61+
userAgentDict = json.loads(urllib.parse.unquote(userAgent[1]))
62+
csvString += userAgentDict['implementation']['name'] + ','
63+
csvString += userAgentDict['implementation']['version'] + ','
64+
csvString += userAgentDict['distro']['name'] + ','
65+
csvString += userAgentDict['distro']['version'] + ','
66+
csvString += userAgentDict['system']['name'] + ','
67+
csvString += userAgentDict['system']['release'] + ','
68+
csvString += userAgentDict['cpu'] + ','
69+
if "be_geo_id" in userAgentDict:
70+
csvString += userAgentDict['be_geo_id'] + ','
71+
else:
72+
csvString += ','
73+
if "application" in userAgentDict:
74+
csvString += userAgentDict['application'] + ','
75+
elif "caller" in userAgentDict:
76+
csvString += userAgentDict['caller'] + ','
77+
else:
78+
csvString += ','
79+
else:
80+
csvString += ',,,,,,,,,'
81+
else:
82+
csvString += ',,,,,,,,,'
83+
else:
84+
csvString += use['userAgent']+ ','
85+
csvString += ',,,,,,,,,'
86+
87+
csvString += '\r\n'
88+
89+
# Output the file
90+
now = datetime.now()
91+
dt_string = now.strftime("%Y-%m-%d_%H-%M-%S")
92+
filename = org_id + '_' + str(timespan) + '_' + dt_string + '.csv'
93+
file = open(filename, 'w')
94+
file.write(csvString)
95+
file.close()
96+
print('Results written to ' + filename)
97+
98+
if __name__ == '__main__':
99+
# First check for API key
100+
if "MERAKI_DASHBOARD_API_KEY" not in os.environ:
101+
print('You must set the MERAKI_DASHBOARD_API_KEY variable')
102+
sys.exit()
103+
104+
# Now check arguments
105+
parser = argparse.ArgumentParser(description='Generate a CSV file of Meraki API activity for an organization.')
106+
parser.add_argument('org_id', help='Organization id to pull API activity from')
107+
parser.add_argument('--timespan', type=int, default=900,
108+
help='The timespan (in seconds) for which the information will be fetched. Default = 900 (15 mins)')
109+
args = parser.parse_args()
110+
print('About to run with org_id: ' + args.org_id + ' and timespan: ' + str(args.timespan))
111+
112+
# Finally, let's roll
113+
start_time = datetime.now()
114+
main(args.org_id, args.timespan)
115+
end_time = datetime.now()
116+
print(f'\nScript complete, total runtime {end_time - start_time}')

meraki_v1/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class DashboardAPI(object):
4242
- print_console (boolean): print logging output to console?
4343
- suppress_logging (boolean): disable all logging? you're on your own then!
4444
- simulate (boolean): simulate POST/PUT/DELETE calls to prevent changes?
45+
- be_geo_id (string): optional partner identifier for API usage tracking; can also be set as an environment variable BE_GEO_ID
46+
- caller (string): optional identifier for API usage tracking; can also be set as an environment variable MERAKI_PYTHON_SDK_CALLER
4547
"""
4648

4749
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
5153
retry_4xx_error=RETRY_4XX_ERROR, retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME,
5254
maximum_retries=MAXIMUM_RETRIES, output_log=OUTPUT_LOG, log_path=LOG_PATH,
5355
log_file_prefix=LOG_FILE_PREFIX, print_console=PRINT_TO_CONSOLE, suppress_logging=SUPPRESS_LOGGING,
54-
simulate=SIMULATE_API_CALLS):
56+
simulate=SIMULATE_API_CALLS, be_geo_id='', caller=''):
5557
# Check API key
5658
api_key = api_key or os.environ.get(API_KEY_ENVIRONMENT_VARIABLE)
5759
if not api_key:
5860
raise APIKeyError()
5961

62+
# Pull the BE GEO ID from an environment variable if present
63+
be_geo_id = be_geo_id or os.environ.get('BE_GEO_ID')
64+
65+
# Pull the caller from an environment variable if present
66+
caller = caller or os.environ.get('MERAKI_PYTHON_SDK_CALLER')
67+
6068
# Configure logging
6169
if not suppress_logging:
6270
self._logger = logging.getLogger(__name__)
@@ -97,6 +105,8 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo
97105
retry_4xx_error_wait_time=retry_4xx_error_wait_time,
98106
maximum_retries=maximum_retries,
99107
simulate=simulate,
108+
be_geo_id=be_geo_id,
109+
caller=caller,
100110
)
101111

102112
# API endpoints by section

meraki_v1/rest_session.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import requests
66

7+
import urllib.parse
8+
import platform
9+
710
from .config import *
811
from .exceptions import *
912

@@ -24,6 +27,8 @@ def __init__(
2427
retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME,
2528
maximum_retries=MAXIMUM_RETRIES,
2629
simulate=SIMULATE_API_CALLS,
30+
be_geo_id='',
31+
caller=''
2732
):
2833
super(RestSession, self).__init__()
2934

@@ -39,6 +44,8 @@ def __init__(
3944
self._retry_4xx_error_wait_time = retry_4xx_error_wait_time
4045
self._maximum_retries = maximum_retries
4146
self._simulate = simulate
47+
self._be_geo_id = be_geo_id
48+
self._caller = caller
4249

4350
# Initialize a new `requests` session
4451
self._req_session = requests.session()
@@ -50,11 +57,53 @@ def __init__(
5057
elif self._base_url[-1] == '/':
5158
self._base_url = self._base_url[:-1]
5259

60+
# Generate extended portion of the User-Agent
61+
user_agent_extended = be_geo_id
62+
user_agent_extended = {}
63+
64+
# Mimic pip system data collection per https://github.com/pypa/pip/blob/master/src/pip/_internal/network/session.py
65+
user_agent_extended['implementation'] = {
66+
"name": platform.python_implementation(),
67+
}
68+
69+
if user_agent_extended["implementation"]["name"] == 'CPython':
70+
user_agent_extended["implementation"]["version"] = platform.python_version()
71+
elif user_agent_extended["implementation"]["name"] == 'PyPy':
72+
if sys.pypy_version_info.releaselevel == 'final':
73+
pypy_version_info = sys.pypy_version_info[:3]
74+
else:
75+
pypy_version_info = sys.pypy_version_info
76+
user_agent_extended["implementation"]["version"] = ".".join(
77+
[str(x) for x in pypy_version_info]
78+
)
79+
elif user_agent_extended["implementation"]["name"] == 'Jython':
80+
user_agent_extended["implementation"]["version"] = platform.python_version()
81+
elif user_agent_extended["implementation"]["name"] == 'IronPython':
82+
user_agent_extended["implementation"]["version"] = platform.python_version()
83+
84+
if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
85+
user_agent_extended["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
86+
87+
if platform.system():
88+
user_agent_extended.setdefault("system", {})["name"] = platform.system()
89+
90+
if platform.release():
91+
user_agent_extended.setdefault("system", {})["release"] = platform.release()
92+
93+
if platform.machine():
94+
user_agent_extended["cpu"] = platform.machine()
95+
96+
if be_geo_id:
97+
user_agent_extended["be_geo_id"] = be_geo_id
98+
99+
if caller:
100+
user_agent_extended["caller"] = caller
101+
53102
# Update the headers for the session
54103
self._req_session.headers = {
55104
'Authorization': 'Bearer ' + self._api_key,
56105
'Content-Type': 'application/json',
57-
'User-Agent': 'python-meraki/1.0.0b1',
106+
'User-Agent': 'python-meraki/1.0.0b1' + urllib.parse.quote(json.dumps(user_agent_extended)),
58107
}
59108

60109
# Log API calls

0 commit comments

Comments
 (0)