Skip to content

Commit 9dd2941

Browse files
author
Shiyue Cheng
authored
Merge pull request meraki#88 from nerdguru/master
Enhanced User-Agent
2 parents 8646747 + 97f375a commit 9dd2941

6 files changed

Lines changed: 350 additions & 4 deletions

File tree

examples/apiData2CSV_v0.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/v0/',
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.api_usage.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}')

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_v0/__init__.py

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

113115
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
117119
retry_4xx_error=RETRY_4XX_ERROR, retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME,
118120
maximum_retries=MAXIMUM_RETRIES, output_log=OUTPUT_LOG, log_path=LOG_PATH,
119121
log_file_prefix=LOG_FILE_PREFIX, print_console=PRINT_TO_CONSOLE, suppress_logging=SUPPRESS_LOGGING,
120-
simulate=SIMULATE_API_CALLS):
122+
simulate=SIMULATE_API_CALLS, be_geo_id='', caller=''):
121123
# Check API key
122124
api_key = api_key or os.environ.get(API_KEY_ENVIRONMENT_VARIABLE)
123125
if not api_key:
124126
raise APIKeyError()
125127

128+
# Pull the BE GEO ID from an environment variable if present
129+
be_geo_id = be_geo_id or os.environ.get('BE_GEO_ID')
130+
131+
# Pull the caller from an environment variable if present
132+
caller = caller or os.environ.get('MERAKI_PYTHON_SDK_CALLER')
133+
126134
# Configure logging
127135
if not suppress_logging:
128136
self._logger = logging.getLogger(__name__)
@@ -163,6 +171,8 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo
163171
retry_4xx_error_wait_time=retry_4xx_error_wait_time,
164172
maximum_retries=maximum_retries,
165173
simulate=simulate,
174+
be_geo_id=be_geo_id,
175+
caller=caller,
166176
)
167177

168178
# API endpoints by section

meraki_v0/rest_session.py

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

55
import requests
66

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

13+
def user_agent_extended(be_geo_id, caller):
14+
# Generate extended portion of the User-Agent
15+
user_agent_extended = be_geo_id
16+
user_agent_extended = {}
17+
18+
# Mimic pip system data collection per https://github.com/pypa/pip/blob/master/src/pip/_internal/network/session.py
19+
user_agent_extended['implementation'] = {
20+
"name": platform.python_implementation(),
21+
}
22+
23+
if user_agent_extended["implementation"]["name"] in ('CPython','Jython','IronPython'):
24+
user_agent_extended["implementation"]["version"] = platform.python_version()
25+
elif user_agent_extended["implementation"]["name"] == 'PyPy':
26+
if sys.pypy_version_info.releaselevel == 'final':
27+
pypy_version_info = sys.pypy_version_info[:3]
28+
else:
29+
pypy_version_info = sys.pypy_version_info
30+
user_agent_extended["implementation"]["version"] = ".".join(
31+
[str(x) for x in pypy_version_info]
32+
)
33+
34+
if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
35+
user_agent_extended["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
36+
37+
if platform.system():
38+
user_agent_extended.setdefault("system", {})["name"] = platform.system()
39+
40+
if platform.release():
41+
user_agent_extended.setdefault("system", {})["release"] = platform.release()
42+
43+
if platform.machine():
44+
user_agent_extended["cpu"] = platform.machine()
45+
46+
if be_geo_id:
47+
user_agent_extended["be_geo_id"] = be_geo_id
48+
49+
if caller:
50+
user_agent_extended["caller"] = caller
51+
52+
return urllib.parse.quote(json.dumps(user_agent_extended))
1053

1154
# Main module interface
1255
class RestSession(object):
@@ -24,6 +67,8 @@ def __init__(
2467
retry_4xx_error_wait_time=RETRY_4XX_ERROR_WAIT_TIME,
2568
maximum_retries=MAXIMUM_RETRIES,
2669
simulate=SIMULATE_API_CALLS,
70+
be_geo_id='',
71+
caller=''
2772
):
2873
super(RestSession, self).__init__()
2974

@@ -39,6 +84,8 @@ def __init__(
3984
self._retry_4xx_error_wait_time = retry_4xx_error_wait_time
4085
self._maximum_retries = maximum_retries
4186
self._simulate = simulate
87+
self._be_geo_id = be_geo_id
88+
self._caller = caller
4289

4390
# Initialize a new `requests` session
4491
self._req_session = requests.session()
@@ -54,7 +101,7 @@ def __init__(
54101
self._req_session.headers = {
55102
'X-Cisco-Meraki-API-Key': self._api_key,
56103
'Content-Type': 'application/json',
57-
'User-Agent': 'python-meraki/0.100.2',
104+
'User-Agent': 'python-meraki/0.100.2' + user_agent_extended(be_geo_id, caller),
58105
}
59106

60107
# Log API calls

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

0 commit comments

Comments
 (0)