__init__.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. from __future__ import print_function
  2. #
  3. # Copyright (c) 2018 Joe Clarke <jclarke@cisco.com>
  4. #
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions
  7. # are met:
  8. # 1. Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # 2. Redistributions in binary form must reproduce the above copyright
  11. # notice, this list of conditions and the following disclaimer in the
  12. # documentation and/or other materials provided with the distribution.
  13. #
  14. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  15. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  17. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  18. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  19. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  20. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  21. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  22. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  23. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  24. # SUCH DAMAGE.
  25. from builtins import str
  26. from builtins import object
  27. import requests
  28. import logging
  29. import json
  30. class Meraki(object):
  31. MERAKI_API = 'https://api.meraki.com/api/v0'
  32. _headers = {
  33. 'X-Cisco-Meraki-API-Key': None,
  34. 'Content-type': 'application/json'
  35. }
  36. _logit = False
  37. _key = None
  38. _name = None
  39. _id = None
  40. _class_name = 'Meraki'
  41. _name_prop = 'name'
  42. _id_prop = 'id'
  43. _api_url_endpoint = ''
  44. def __init__(self, **kwargs):
  45. if 'logit' in kwargs:
  46. self._logit = kwargs['logit']
  47. if 'key' in kwargs:
  48. self._key = kwargs['key']
  49. self._headers['X-Cisco-Meraki-API-Key'] = kwargs['key']
  50. if 'name' in kwargs:
  51. self._name = kwargs['name']
  52. if 'id' in kwargs:
  53. self._id = str(kwargs['id'])
  54. self.__dict = {}
  55. self._initialized = False
  56. def set_key(self, key):
  57. self._key = key
  58. self._headers['X-Cisco-Meraki-API-Key'] = key
  59. def set_name(self, name):
  60. self._name = name
  61. def _set_id(self, id):
  62. self._id = id
  63. def __check_headers(self):
  64. if self._headers['X-Cisco-Meraki-API-Key'] is None:
  65. msg = 'Meraki API key is not set!'
  66. if self._logit:
  67. logging.error(msg)
  68. else:
  69. print(msg)
  70. return False
  71. return True
  72. def realize(self):
  73. return self._check_obj()
  74. @staticmethod
  75. def _get_json_errors(response):
  76. res = ''
  77. try:
  78. jobj = response.json()
  79. if 'errors' in jobj:
  80. res = ', '.join(jobj['errors'])
  81. except:
  82. pass
  83. return res
  84. def _check_obj(self):
  85. if self._initialized:
  86. return True
  87. if self._id is None:
  88. msg = '{} id is not set!'.format(self._class_name)
  89. if self._logit:
  90. logging.error(msg)
  91. else:
  92. print(msg)
  93. return False
  94. return self.__get_current_obj()
  95. def __get_current_obj(self):
  96. if not self.__check_headers():
  97. return False
  98. url = self.MERAKI_API + self._api_url_endpoint + '/' + self._id
  99. try:
  100. response = requests.request('GET', url, headers=self._headers)
  101. response.raise_for_status()
  102. except Exception as e:
  103. msg = 'Error getting {} for {}: {} ({})'.format(
  104. self._class_name.lower(), self._id, e, Meraki._get_json_errors(response))
  105. if self._logit:
  106. logging.error(msg)
  107. else:
  108. print(msg)
  109. return False
  110. jobj = response.json()
  111. self.__populate_obj(jobj)
  112. # Object may not have a name yet.
  113. if self._name_prop in jobj:
  114. self._name = jobj[self._name_prop]
  115. self._initialized = True
  116. return True
  117. def __populate_obj(self, jobj):
  118. for k, v in list(jobj.items()):
  119. self.__dict[k] = v
  120. def get(self, field):
  121. if not self._check_obj():
  122. raise Exception('Failed to initialize object!')
  123. if field in self.__dict:
  124. return self.__dict[field]
  125. else:
  126. raise Exception('Field {} does not exist'.format(field))
  127. def set(self, field, value):
  128. if not self._check_obj():
  129. raise Exception('Failed to initialize object!')
  130. self.__dict[field] = value
  131. def get_organizations(self):
  132. if not self.__check_headers():
  133. return None
  134. url = self.MERAKI_API + '/organizations'
  135. try:
  136. response = requests.request('GET', url, headers=self._headers)
  137. response.raise_for_status()
  138. except Exception as e:
  139. msg = 'Error getting list of organizations: {} ({})'.format(
  140. e, Meraki._get_json_errors(response))
  141. if self._logit:
  142. logging.error(msg)
  143. else:
  144. print(msg)
  145. return None
  146. res = []
  147. for org in response.json():
  148. org_obj = Organization(
  149. key=self._key, name=org[Organization._name_prop], id=org[Organization._id_prop])
  150. res.append(org_obj)
  151. return res
  152. class Organization(Meraki):
  153. def __init__(self, **kwargs):
  154. super(Organization, self).__init__(**kwargs)
  155. self._class_name = 'Organization'
  156. self._api_url_endpoint = '/organizations'
  157. self.ORG_API = self.MERAKI_API + self._api_url_endpoint
  158. def get_inventory(self):
  159. if not self._check_obj():
  160. return None
  161. url = self.ORG_API + '/' + self._id + '/inventory'
  162. try:
  163. response = requests.request('GET', url, headers=self._headers)
  164. response.raise_for_status()
  165. except Exception as e:
  166. msg = 'Error getting inventory for organization {}: {} ({})'.format(
  167. self._name, e, Meraki._get_json_errors(response))
  168. if self._logit:
  169. logging.error(msg)
  170. else:
  171. print(msg)
  172. return None
  173. return response.json()
  174. def get_networks(self):
  175. if not self._check_obj():
  176. return None
  177. url = self.ORG_API + '/' + self._id + '/networks'
  178. try:
  179. response = requests.request('GET', url, headers=self._headers)
  180. response.raise_for_status()
  181. except Exception as e:
  182. msg = 'Error getting list of networks for {}: {} ({})'.format(
  183. self._name, e, Meraki._get_json_errors(response))
  184. if self._logit:
  185. logging.error(msg)
  186. else:
  187. print(msg)
  188. return None
  189. res = []
  190. for n in response.json():
  191. net = Network(
  192. key=self._key, name=n[Network._name_prop], id=n[Network._id_prop
  193. ])
  194. res.append(net)
  195. return res
  196. def create_network(self, name, **kwargs):
  197. if not self._check_obj():
  198. return None
  199. payload = {
  200. 'name': name
  201. }
  202. for key in ['type', 'tags', 'timezone', 'copy_from_network_id']:
  203. if key in kwargs:
  204. if key == 'timezone':
  205. payload['timeZone'] = kwargs[key]
  206. elif key == 'copy_from_network_id':
  207. payload['copyFromNetworkId'] = kwargs[key]
  208. else:
  209. payload[key] = kwargs[key]
  210. if 'type' not in kwargs:
  211. payload['type'] = 'wireless switch appliance'
  212. url = self.ORG_API + '/' + self._id + '/networks'
  213. try:
  214. response = requests.request(
  215. 'POST', url, json=payload, headers=self._headers)
  216. response.raise_for_status()
  217. except Exception as e:
  218. msg = 'Error creating new network {} in {}: {} ({})'.format(
  219. name, self._name, e, Meraki._get_json_errors(response))
  220. if self._logit:
  221. logging.error(msg)
  222. else:
  223. print(msg)
  224. return None
  225. jobj = response.json()
  226. net_obj = Network(key=self._key, id=jobj['id'], name=jobj['name'])
  227. return net_obj
  228. class Network(Meraki):
  229. def __init__(self, **kwargs):
  230. super(Network, self).__init__(**kwargs)
  231. self._class_name = 'Network'
  232. self._api_url_endpoint = '/networks'
  233. self.NET_API = self.MERAKI_API + self._api_url_endpoint
  234. def get_devices(self):
  235. if not self._check_obj():
  236. return None
  237. url = self.NET_API + '/' + self._id + '/devices'
  238. try:
  239. response = requests.request('GET', url, headers=self._headers)
  240. response.raise_for_status()
  241. except Exception as e:
  242. msg = 'Error getting device list for network {}: {} ({})'.format(
  243. self._name, e, Meraki._get_json_errors(response))
  244. if self._logit:
  245. logging.error(msg)
  246. else:
  247. print(msg)
  248. return None
  249. res = []
  250. for d in response.json():
  251. dev = Device(
  252. key=self._key, name=d[Device._name_prop], id=d[Device._id_prop])
  253. res.append(dev)
  254. return res
  255. def claim_device(self, dev):
  256. if not self._check_obj():
  257. return None
  258. url = self.NET_API + '/' + self._id + '/devices/claim'
  259. payload = {
  260. 'serial': dev._id
  261. }
  262. try:
  263. response = requests.request(
  264. 'POST', url, json=payload, headers=self._headers)
  265. response.raise_for_status()
  266. except Exception as e:
  267. msg = 'Error claiming device {} for network {}: {} ({})'.format(
  268. dev._id, self._name, e, Meraki._get_json_errors(response))
  269. if self._logit:
  270. logging.error(msg)
  271. else:
  272. print(msg)
  273. return None
  274. new_dev = Device(key=self._key, id=dev._id, net=self)
  275. return new_dev
  276. def create_vlan(self, name, id, subnet, appliance_ip):
  277. if not self._check_obj():
  278. return None
  279. payload = {
  280. 'id': id,
  281. 'name': name,
  282. 'subnet': subnet,
  283. 'applianceIp': appliance_ip
  284. }
  285. url = self.NET_API + '/' + self._id + '/vlans'
  286. try:
  287. response = requests.request(
  288. 'POST', url, json=payload, headers=self._headers)
  289. response.raise_for_status()
  290. except Exception as e:
  291. msg = 'Error adding VLAN {} (ID: {}) to network {}: {} ({})'.format(
  292. name, id, self._name, e, Meraki._get_json_errors(response))
  293. if self._logit:
  294. logging.error(msg)
  295. else:
  296. print(msg)
  297. return None
  298. jobj = response.json()
  299. vlan_obj = Vlan(
  300. key=self._key, id=jobj['id'], name=jobj['name'], net=self)
  301. return vlan_obj
  302. class SSID(Meraki):
  303. _id_prop = 'number'
  304. def __init__(self, **kwargs):
  305. super(SSID, self).__init__(**kwargs)
  306. if not 'net' in kwargs:
  307. raise TypeError('Missing mandatory net argument!')
  308. self._net = kwargs['net']
  309. self._class_name = 'SSID'
  310. self._api_url_endpoint = '/networks/' + \
  311. self._net.get(Network._id_prop) + '/ssids'
  312. self.SSID_API = self.MERAKI_API + self._api_url_endpoint
  313. def update_ssid(self, **kwargs):
  314. if not self._check_obj():
  315. return False
  316. payload = {}
  317. for key in ['name', 'enabled', 'auth_mode', 'encryption_mode', 'psk', 'ip_assignment_mode']:
  318. if key in kwargs:
  319. if key == 'auth_mode':
  320. payload['authMode'] = kwargs[key]
  321. elif key == 'encryption_mode':
  322. payload['encryptionMode'] = kwargs[key]
  323. elif key == 'ip_assignment_mode':
  324. payload['ipAssignmentMode'] = kwargs[key]
  325. else:
  326. payload[key] = kwargs[key]
  327. if len(payload) == 0:
  328. return False
  329. if 'ip_assignment_mode' not in kwargs:
  330. payload['ipAssignmentMode'] = 'Bridge mode'
  331. url = self.SSID_API + '/' + self._id
  332. try:
  333. response = requests.request(
  334. 'PUT', url, json=payload, headers=self._headers)
  335. response.raise_for_status()
  336. except Exception as e:
  337. msg = 'Error updating SSID properties for {}: {} ({})'.format(
  338. self._id, e, Meraki._get_json_errors(response))
  339. if self._logit:
  340. logging.error(msg)
  341. else:
  342. print(msg)
  343. return False
  344. jobj = response.json()
  345. for k, v in list(jobj.items()):
  346. self.set(k, v)
  347. return True
  348. class Vlan(Meraki):
  349. def __init__(self, **kwargs):
  350. super(Vlan, self).__init__(**kwargs)
  351. if not 'net' in kwargs:
  352. raise TypeError('Missing mandatory net argument!')
  353. self._net = kwargs['net']
  354. self._class_name = 'Vlan'
  355. self._api_url_endpoint = '/networks/' + \
  356. self._net.get(Network._id_prop) + '/vlans'
  357. self.VLAN_API = self.MERAKI_API + self._api_url_endpoint
  358. def update_vlan(self, **kwargs):
  359. if not self._check_obj():
  360. return False
  361. payload = {}
  362. for key in ['name', 'subnet', 'appliance_ip', 'fixed_ip_assignments', 'reserved_ip_ranges', 'dns_nameservers']:
  363. if key in kwargs:
  364. if key == 'appliance_ip':
  365. payload['applianceIp'] = kwargs[key]
  366. elif key == 'fixed_ip_assignments':
  367. payload['fixedIpAssignments'] = kwargs[key]
  368. elif key == 'reserved_ip_ranges':
  369. payload['reservedIpRanges'] = kwargs[key]
  370. elif key == 'dns_nameservers':
  371. payload['dnsNameservers'] = kwargs[key]
  372. else:
  373. payload[key] = kwargs[key]
  374. if len(payload) == 0:
  375. return False
  376. url = self.VLAN_API + '/' + self._id
  377. try:
  378. response = requests.request(
  379. 'PUT', url, json=payload, headers=self._headers)
  380. response.raise_for_status()
  381. except Exception as e:
  382. msg = 'Error updating VLAN properties for {}: {} ({})'.format(
  383. self._name, e, Meraki._get_json_errors(response))
  384. if self._logit:
  385. logging.error(msg)
  386. else:
  387. print(msg)
  388. return False
  389. jobj = response.json()
  390. for k, v in list(jobj.items()):
  391. self.set(k, v)
  392. return True
  393. class Device(Meraki):
  394. _id_prop = 'serial'
  395. def __init__(self, **kwargs):
  396. super(Device, self).__init__(**kwargs)
  397. if not 'net' in kwargs:
  398. raise TypeError('Missing mandatory net argument!')
  399. self._net = kwargs['net']
  400. self._class_name = 'Device'
  401. self._api_url_endpoint = '/networks/' + \
  402. self._net.get(Network._id_prop) + '/devices'
  403. self.DEV_API = self.MERAKI_API + self._api_url_endpoint
  404. def remove_device(self):
  405. if not self._check_obj():
  406. return False
  407. url = self.DEV_API + '/' + self._id + '/remove'
  408. try:
  409. response = requests.request('POST', url, headers=self._headers)
  410. response.raise_for_status()
  411. except Exception as e:
  412. msg = 'Failed to remove device {} for network {}: {} ({})'.format(
  413. self._name, self._net.get(Network._name_prop), e, Meraki._get_json_errors(response))
  414. if self._logit:
  415. logging.error(msg)
  416. else:
  417. print(msg)
  418. return False
  419. return True
  420. def update_device(self, **kwargs):
  421. if not self._check_obj():
  422. return False
  423. payload = {}
  424. for key in ['name', 'tags', 'lat', 'lng', 'address', 'move_map_marker']:
  425. if key in kwargs:
  426. if key == 'move_map_marker':
  427. payload['moveMapMarker'] = kwargs[key]
  428. else:
  429. payload[key] = kwargs[key]
  430. if len(payload) == 0:
  431. return False
  432. url = self.DEV_API + '/' + self._id
  433. try:
  434. response = requests.request(
  435. 'PUT', url, json=payload, headers=self._headers)
  436. response.raise_for_status()
  437. except Exception as e:
  438. msg = 'Error updating device properties for {}: {} ({})'.format(
  439. self._name, e, Meraki._get_json_errors(response))
  440. if self._logit:
  441. logging.error(msg)
  442. else:
  443. print(msg)
  444. return False
  445. jobj = response.json()
  446. for k, v in list(jobj.items()):
  447. if k == 'moveMapMarker':
  448. continue
  449. self.set(k, v)
  450. return True
  451. class SwitchPort(Meraki):
  452. _id_prop = 'number'
  453. def __init__(self, **kwargs):
  454. super(SwitchPort, self).__init__(**kwargs)
  455. if not 'dev' in kwargs:
  456. raise TypeError('Missing mandatory dev argument!')
  457. self._dev = kwargs['dev']
  458. self._class_name = 'SwitchPort'
  459. self._api_url_endpoint = '/devices/' + \
  460. self._net.get(Network._id_prop) + '/switchPorts'
  461. self.SWP_API = self.MERAKI_API + self._api_url_endpoint
  462. def update_switchport(self, **kwargs):
  463. if not self._check_obj():
  464. return False
  465. payload = {}
  466. for key in ['name', 'tags', 'enabled', 'type', 'vlan', 'voice_vlan', 'allowed_vlans', 'poe_enabled']:
  467. if key in kwargs:
  468. if key == 'voice_vlan':
  469. payload['voiceVlan'] = kwargs[key]
  470. elif key == 'allowed_vlans':
  471. payload['allowedVlans'] = kwargs[key]
  472. elif key == 'poe_enabled':
  473. payload['poeEnabled'] = kwargs[key]
  474. else:
  475. payload[key] = kwargs[key]
  476. if len(payload) == 0:
  477. return False
  478. url = self.SWP_API + '/' + self._id
  479. try:
  480. response = requests.request(
  481. 'PUT', url, json=payload, headers=self._headers)
  482. response.raise_for_status()
  483. except Exception as e:
  484. msg = 'Error updating switchport properties for {} on device {}: {} ({})'.format(
  485. self._id, self._dev.get(Device._name_prop), e, Meraki._get_json_errors(response))
  486. if self._logit:
  487. logging.error(msg)
  488. else:
  489. print(msg)
  490. return False
  491. jobj = response.json()
  492. for k, v in list(jobj.items()):
  493. self.set(k, v)
  494. return True