netbox-export-to-google.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. #!/usr/bin/env python
  2. from googleapiclient.discovery import build
  3. from google.auth.transport.requests import Request
  4. from google.oauth2.credentials import Credentials
  5. import pynetbox
  6. import os
  7. from typing import Any
  8. import traceback
  9. import netaddr
  10. import ipaddress
  11. import CLEUCreds # type: ignore
  12. from cleu.config import Config as C # type: ignore
  13. CREDS_FILE = "gs_token.json"
  14. SHEET_ID = "1kKOqbK_y3l6Ume-MSkLg1nTVbw571mcq3EOQw5IWuWQ"
  15. def export_ips(nb: Any, gs_service: Any) -> None:
  16. """Export NetBox IPs to a Google Sheet"""
  17. # Get all IP addresses from NetBox
  18. ips = list(nb.ipam.ip_addresses.all())
  19. new_values = []
  20. headers = [
  21. "Address",
  22. "VRF",
  23. "Status",
  24. "Role",
  25. "Tenant",
  26. "Assigned",
  27. "DNS name",
  28. "Description",
  29. "ID",
  30. "Tenant Group",
  31. "NAT (Inside)",
  32. "NAT (Outside)",
  33. "Comments",
  34. "Tags",
  35. "Created",
  36. "Last updated",
  37. "Interface",
  38. "Parent",
  39. "List of additional CNAMEs",
  40. ]
  41. new_values.append(headers)
  42. for ip in ips:
  43. tenant = ""
  44. tenant_group = ""
  45. if ip.tenant:
  46. ip.tenant.full_details()
  47. tenant = ip.tenant.name
  48. if ip.tenant.group:
  49. tenant_group = str(ip.tenant.group)
  50. parent = ""
  51. if ip.assigned_object:
  52. if ip.assigned_object_type == "virtualization.vminterface":
  53. parent = ip.assigned_object.virtual_machine.name
  54. elif ip.assigned_object_type == "dcim.interface":
  55. parent = ip.assigned_object.device.name
  56. role = ""
  57. if ip.role:
  58. role = str(ip.role)
  59. nat_inside = ""
  60. if ip.nat_inside:
  61. nat_inside = str(nat_inside)
  62. nat_outside = ""
  63. if len(ip.nat_outside) > 0:
  64. nat_outside = ",".join(ip.nat_outside)
  65. vrf = ""
  66. if ip.vrf:
  67. vrf = str(ip.vrf)
  68. tags = ""
  69. if len(ip.tags) > 0:
  70. tags = ",".join(ip.tags)
  71. interface = ""
  72. if ip.assigned_object:
  73. interface = str(ip.assigned_object)
  74. row = {
  75. "Address": ip.address,
  76. "VRF": vrf,
  77. "Status": ip.status.label,
  78. "Role": role,
  79. "Tenant": tenant,
  80. "Assigned": ip.assigned_object_id,
  81. "DNS name": ip.dns_name,
  82. "Description": ip.description,
  83. "ID": ip.id,
  84. "Tenant Group": tenant_group,
  85. "NAT (Inside)": nat_inside,
  86. "NAT (Outside)": nat_outside,
  87. "Comments": ip.comments,
  88. "Tags": tags,
  89. "Created": str(ip.created),
  90. "Last updated": str(ip.last_updated),
  91. "Interface": interface,
  92. "Parent": parent,
  93. "List of additional CNAMEs": ip.custom_fields["CNAMEs"],
  94. }
  95. new_values.append(list(row.values()))
  96. ip_sheet = gs_service.spreadsheets()
  97. ip_sheet.values().update(
  98. spreadsheetId=SHEET_ID, range="IP Addresses!A1:ZZ", body={"values": new_values}, valueInputOption="RAW"
  99. ).execute()
  100. def _get_prefix_utilization(prefix: Any, nb: Any) -> float:
  101. """Get the utilization of a prefix"""
  102. if prefix.mark_utilized:
  103. return 100.0
  104. prefix_size = ipaddress.ip_network(prefix.prefix).num_addresses
  105. child_ips = nb.ipam.ip_addresses.filter(parent=prefix.prefix, vrf_id=prefix.vrf.id)
  106. prefixlen = ipaddress.ip_network(prefix.prefix).prefixlen
  107. if prefix.status.label.lower() == "container":
  108. queryset = nb.ipam.prefixes.filter(within=prefix.prefix, vrf_id=prefix.vrf.id)
  109. child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
  110. utilization = float(child_prefixes.size) / prefix_size * 100
  111. else:
  112. child_ipset = netaddr.IPSet([_.address for _ in child_ips])
  113. if prefix.family.label == "IPv4" and prefixlen < 31 and not prefix.is_pool:
  114. prefix_size -= 2
  115. utilization = float(child_ipset.size) / prefix_size * 100
  116. return min(utilization, 100)
  117. def export_prefixes(nb: Any, gs_service: Any) -> None:
  118. """Export NetBox IP prefixes to a Google Sheet"""
  119. # Get all IP prefixes from NetBox
  120. prefixes = list(nb.ipam.prefixes.all())
  121. new_values = []
  122. headers = [
  123. "Prefix",
  124. "Status",
  125. "Children",
  126. "VRF",
  127. "Utilization",
  128. "Tenant",
  129. "Site",
  130. "VLAN",
  131. "Role",
  132. "Description",
  133. "Pool",
  134. "ID",
  135. "Prefix (Flat)",
  136. "Tenant Group",
  137. "VLAN Group",
  138. "Mark Utilized",
  139. "Comments",
  140. "Tags",
  141. "Created",
  142. "Last updated",
  143. "Depth",
  144. ]
  145. new_values.append(headers)
  146. for prefix in prefixes:
  147. tenant = ""
  148. tenant_group = ""
  149. if prefix.tenant:
  150. prefix.tenant.full_details()
  151. tenant = prefix.tenant.name
  152. if prefix.tenant.group:
  153. tenant_group = str(prefix.tenant.group)
  154. vlan = ""
  155. vlan_group = ""
  156. if prefix.vlan:
  157. prefix.vlan.full_details()
  158. vlan = f"{prefix.vlan.name} ({prefix.vlan.vid})"
  159. if prefix.vlan.group:
  160. vlan_group = str(prefix.vlan.group)
  161. site = ""
  162. if prefix.site:
  163. site = prefix.site.name
  164. role = ""
  165. if prefix.role:
  166. role = str(prefix.role)
  167. vrf = ""
  168. if prefix.vrf:
  169. vrf = str(prefix.vrf)
  170. tags = ""
  171. if len(prefix.tags) > 0:
  172. tags = ",".join(prefix.tags)
  173. row = {
  174. "Prefix": prefix.prefix,
  175. "Status": prefix.status.label,
  176. "Children": prefix.children,
  177. "VRF": vrf,
  178. "Utilized": "%.2f" % _get_prefix_utilization(prefix, nb) + "%",
  179. "Tenant": tenant,
  180. "Site": site,
  181. "VLAN": vlan,
  182. "Role": role,
  183. "Description": prefix.description,
  184. "Pool": prefix.is_pool,
  185. "ID": prefix.id,
  186. "Prefix (Flat)": prefix.prefix,
  187. "Tenant Group": tenant_group,
  188. "VLAN Group": vlan_group,
  189. "Mark Utilized": prefix.mark_utilized,
  190. "Comments": prefix.comments,
  191. "Tags": tags,
  192. "Created": str(prefix.created),
  193. "Last updated": str(prefix.last_updated),
  194. "Depth": prefix._depth,
  195. }
  196. new_values.append(list(row.values()))
  197. prefix_sheet = gs_service.spreadsheets()
  198. prefix_sheet.values().update(
  199. spreadsheetId=SHEET_ID, range="IP Prefixes!A1:ZZ", body={"values": new_values}, valueInputOption="RAW"
  200. ).execute()
  201. def export_vlans(nb: Any, gs_service: Any) -> None:
  202. """Export VLANs from NetBox to a Google Sheet"""
  203. # Get all VLANs from NetBox
  204. vlans = list(nb.ipam.vlans.all())
  205. new_values = []
  206. headers = [
  207. "VID",
  208. "Name",
  209. "Site",
  210. "Group",
  211. "Prefixes",
  212. "Tenant",
  213. "Status",
  214. "Role",
  215. "Description",
  216. "ID",
  217. "Tenant Group",
  218. "Comments",
  219. "Tags",
  220. "L2VPN",
  221. "Created",
  222. "Last updated",
  223. ]
  224. new_values.append(headers)
  225. for vlan in vlans:
  226. tenant = ""
  227. tenant_group = ""
  228. if vlan.tenant:
  229. vlan.tenant.full_details()
  230. tenant = vlan.tenant.name
  231. if vlan.tenant.group:
  232. tenant_group = str(vlan.tenant.group)
  233. group = ""
  234. if vlan.group:
  235. group = str(vlan.group)
  236. site = ""
  237. if vlan.site:
  238. site = vlan.site.name
  239. role = ""
  240. if vlan.role:
  241. role = str(vlan.role)
  242. tags = ""
  243. if len(vlan.tags) > 0:
  244. tags = ",".join(vlan.tags)
  245. l2vpn = ""
  246. if vlan.l2vpn_termination:
  247. l2vpn = str(vlan.l2vpn_termination)
  248. prefixes = ",".join([_.prefix for _ in list(nb.ipam.prefixes.filter(vlan_id=vlan.id))])
  249. row = {
  250. "VID": vlan.vid,
  251. "Name": vlan.name,
  252. "Site": site,
  253. "Group": group,
  254. "Prefixes": prefixes,
  255. "Tenant": tenant,
  256. "Status": vlan.status.label,
  257. "Role": role,
  258. "Description": vlan.description,
  259. "ID": vlan.id,
  260. "Tenant Group": tenant_group,
  261. "Comments": vlan.comments,
  262. "Tags": tags,
  263. "L2VPN": l2vpn,
  264. "Created": str(vlan.created),
  265. "Last updated": str(vlan.last_updated),
  266. }
  267. new_values.append(list(row.values()))
  268. vlan_sheet = gs_service.spreadsheets()
  269. vlan_sheet.values().update(spreadsheetId=SHEET_ID, range="VLANs!A1:ZZ", body={"values": new_values}, valueInputOption="RAW").execute()
  270. def main() -> int:
  271. """Export NetBox IP address data to a Google Sheet"""
  272. global creds, SHEET_ID
  273. # Connect to NetBox
  274. nb = pynetbox.api(C.NETBOX_SERVER, CLEUCreds.NETBOX_API_TOKEN)
  275. gs_service = build("sheets", "v4", credentials=creds)
  276. try:
  277. export_ips(nb, gs_service)
  278. except Exception as e:
  279. print(f"ERROR: Failed to export IP addresses to Google Sheets: {e}")
  280. traceback.print_exc()
  281. return 1
  282. try:
  283. export_prefixes(nb, gs_service)
  284. except Exception as e:
  285. print(f"ERROR: Failed to export IP prefixes to Google Sheets: {e}")
  286. traceback.print_exc()
  287. return 1
  288. try:
  289. export_vlans(nb, gs_service)
  290. except Exception as e:
  291. print(f"ERROR: Failed to export VLANs to Google Sheets: {e}")
  292. traceback.print_exc()
  293. return 1
  294. if __name__ == "__main__":
  295. if not os.path.isfile(CREDS_FILE):
  296. print(f"ERROR: Token file {CREDS_FILE} does not exist! Please re-authenticate this app.")
  297. exit(1)
  298. creds = Credentials.from_authorized_user_file(CREDS_FILE, ["https://www.googleapis.com/auth/spreadsheets"])
  299. if not creds.valid:
  300. creds.refresh(Request())
  301. with open(CREDS_FILE, "w") as fd:
  302. fd.write(creds.to_json())
  303. exit(main())