notify_dc_users.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. #!/usr/bin/env python3
  2. from __future__ import print_function
  3. import pickle
  4. import os.path
  5. import os
  6. from googleapiclient.discovery import build
  7. from google_auth_oauthlib.flow import InstalledAppFlow
  8. from google.auth.transport.requests import Request
  9. import smtplib
  10. from email.message import EmailMessage
  11. import sys
  12. import re
  13. import subprocess
  14. import CLEUCreds
  15. from cleu.config import Config as C
  16. FROM = "Joe Clarke <jclarke@cisco.com>"
  17. CC = "Kris Sekula <ksekula@cisco.com>, Anthony Jesani <anjesani@cisco.com>"
  18. JUMP_HOSTS = ["10.100.252.26", "10.100.252.27", "10.100.252.28", "10.100.252.29"]
  19. DC_MAP = {"DC1": "dc1_datastore_1", "DC2": "dc2_datastore_1", "HyperFlex-DC1": "DC1-HX-DS-01", "HyperFlex-DC2": "DC2-HX-DS-01"}
  20. DEFAULT_CLUSTER = "FlexPod"
  21. HX_DCs = {"HyperFlex-DC1": 1, "HyperFlex-DC2": 1}
  22. IP4_SUBNET = "10.100."
  23. IP6_PREFIX = "2a05:f8c0:2:"
  24. NETWORK_MAP = {
  25. "CROSS DC VMs": {
  26. "subnet": "{}252.0/24".format(IP4_SUBNET),
  27. "gw": "{}252.254".format(IP4_SUBNET),
  28. "prefix": "{}64fc::".format(IP6_PREFIX),
  29. "gw6": "{}64fc::fe".format(IP6_PREFIX),
  30. },
  31. "DC1 ONLY VMs": {
  32. "subnet": "{}253.0/24".format(IP4_SUBNET),
  33. "gw": "{}253.254".format(IP4_SUBNET),
  34. "prefix": "{}64fd::".format(IP6_PREFIX),
  35. "gw6": "{}64fd::fe".format(IP6_PREFIX),
  36. },
  37. "DC2 ONLY VMs": {
  38. "subnet": "{}254.0/24".format(IP4_SUBNET),
  39. "gw": "{}254.254".format(IP4_SUBNET),
  40. "prefix": "{}64fe::".format(IP6_PREFIX),
  41. "gw6": "{}64fe::fe".format(IP6_PREFIX),
  42. },
  43. }
  44. OSTYPE_LIST = [
  45. (r"(?i)ubuntu", "ubuntu64Guest"),
  46. (r"(?i)windows 10", "windows9_64Guest"),
  47. (r"(?i)windows 2012", "windows8Server64Guest"),
  48. (r"(?i)windows 201(6|9)", "windows9Server64Guest"),
  49. (r"(?i)debian 8", "debian8_64Guest"),
  50. (r"(?i)debian", "debian9_64Guest"),
  51. (r"(?i)centos 7", "centos7_64Guest"),
  52. (r"(?i)centos", "centos8_64Guest"),
  53. (r"(?i)red hat", "rhel7_64Guest"),
  54. (r"(?i)linux", "other3xLinux64Guest"),
  55. ]
  56. DNS1 = "10.100.253.6"
  57. DNS2 = "10.100.254.6"
  58. NTP1 = "10.128.0.1"
  59. NTP2 = "10.128.0.2"
  60. VCENTER = "https://" + C.VCENTER
  61. DOMAIN = C.DNS_DOMAIN
  62. AD_DOMAIN = C.AD_DOMAIN
  63. SMTP_SERVER = C.SMTP_SERVER
  64. SYSLOG = SMTP_SERVER
  65. ISO_DS = "dc1_datastore_1"
  66. ISO_DS_HX1 = "DC1-HX-DS-01"
  67. ISO_DS_HX2 = "DC2-HX-DS-01"
  68. VPN_SERVER = C.VPN_SERVER
  69. VPN_SERVER_IP = C.VPN_SERVER_IP
  70. ANSIBLE_PATH = "/home/jclarke/src/git/ciscolive/automation/cleu-ansible-n9k"
  71. UPDATE_DNS_PATH = "/home/jclarke"
  72. DATACENTER = "CiscoLive"
  73. CISCOLIVE_YEAR = C.CISCOLIVE_YEAR
  74. PW_RESET_URL = C.PW_RESET_URL
  75. SPREADSHEET_ID = "1ExTNQJ7SArHSJKfPOj_x1O2aTj76dHjlG8kCDHW39hw"
  76. SHEET_HOSTNAME = 0
  77. SHEET_OS = 1
  78. SHEET_OVA = 2
  79. SHEET_CONTACT = 4
  80. SHEET_CPU = 5
  81. SHEET_RAM = 6
  82. SHEET_DISK = 7
  83. SHEET_NICS = 8
  84. SHEET_DC = 11
  85. SHEET_IP = 12
  86. SHEET_VLAN = 13
  87. def main():
  88. if len(sys.argv) != 2:
  89. print("usage: {} ROW_RANGE".format(sys.argv[0]))
  90. sys.exit(1)
  91. if not os.path.exists("gs_token.pickle"):
  92. print("ERROR: Google Sheets token does not exist! Please re-auth the app first.")
  93. sys.exit(1)
  94. creds = None
  95. with open("gs_token.pickle", "rb") as token:
  96. creds = pickle.load(token)
  97. if "VMWARE_USER" not in os.environ or "VMWARE_PASSWORD" not in os.environ:
  98. print("ERROR: VMWARE_USER and VMWARE_PASSWORD environment variables must be set prior to running!")
  99. sys.exit(1)
  100. gs_service = build("sheets", "v4", credentials=creds)
  101. vm_sheet = gs_service.spreadsheets()
  102. vm_result = vm_sheet.values().get(spreadsheetId=SPREADSHEET_ID, range=sys.argv[1]).execute()
  103. vm_values = vm_result.get("values", [])
  104. if not vm_values:
  105. print("ERROR: Did not read anything from Google Sheets!")
  106. sys.exit(1)
  107. (rstart, rend) = sys.argv[1].split(":")
  108. i = int(rstart) - 1
  109. users = {}
  110. for row in vm_values:
  111. i += 1
  112. try:
  113. owners = row[SHEET_CONTACT].strip().split(",")
  114. name = row[SHEET_HOSTNAME].strip()
  115. opsys = row[SHEET_OS].strip()
  116. is_ova = row[SHEET_OVA].strip()
  117. cpu = int(row[SHEET_CPU].strip())
  118. mem = int(row[SHEET_RAM].strip())
  119. disk = int(row[SHEET_DISK].strip())
  120. dc = row[SHEET_DC].strip()
  121. vlan = row[SHEET_VLAN].strip()
  122. ip = row[SHEET_IP].strip()
  123. except Exception as e:
  124. print("WARNING: Failed to process malformed row {}: {}".format(i, e))
  125. continue
  126. if name == "" or ip == "" or dc == "":
  127. print("WARNING: Ignorning malformed row {}".format(i))
  128. continue
  129. for owner in owners:
  130. owner = owner.strip()
  131. if owner not in users:
  132. users[owner] = []
  133. vm = {
  134. "name": name.upper(),
  135. "os": opsys,
  136. "mem": mem,
  137. "is_ova": is_ova,
  138. "cpu": cpu,
  139. "disk": disk,
  140. "vlan": vlan,
  141. "ip": ip,
  142. "dc": dc,
  143. }
  144. users[owner].append(vm)
  145. for user, vms in users.items():
  146. m = re.search(r"<?(\S+)@", user)
  147. username = m.group(1)
  148. body = "Please find the CLEU Data Centre Access details below\r\n\r\n"
  149. body += "Before you can access the Data Centre from remote, AnyConnect to {} and login with {} / {}\r\n".format(
  150. VPN_SERVER, CLEUCreds.VPN_USER, CLEUCreds.VPN_PASS
  151. )
  152. body += "Once connected, your browser should redirect you to the password change tool. If not go to {} and login with {} and password {}\r\n".format(
  153. PW_RESET_URL, username, CLEUCreds.DEFAULT_USER_PASSWORD
  154. )
  155. body += "Reset your password. You must use a complex password that contains lower and uppercase letters, numbers, or a special character.\r\n"
  156. body += "After resetting your password, drop the VPN and reconnect to {} with {} and the new password you just set.\r\n\r\n".format(
  157. VPN_SERVER, username
  158. )
  159. body += "You can use any of the following Windows Jump Hosts to access the data centre using RDP:\r\n\r\n"
  160. for js in JUMP_HOSTS:
  161. body += "{}\r\n".format(js)
  162. body += "\r\nIf a Jump Host is full, try the next one.\r\n\r\n"
  163. body += "Your login is {} (or {}@{} on Windows). Your password is the same you used for the VPN.\r\n\r\n".format(
  164. username, username, AD_DOMAIN
  165. )
  166. body += "The network details for your VM(s) are:\r\n\r\n"
  167. body += "DNS1 : {}\r\n".format(DNS1)
  168. body += "DNS2 : {}\r\n".format(DNS2)
  169. body += "NTP1 : {}\r\n".format(NTP1)
  170. body += "NTP2 : {}\r\n".format(NTP2)
  171. body += "DNS DOMAIN : {}\r\n".format(DOMAIN)
  172. body += "SMTP : {}\r\n".format(SMTP_SERVER)
  173. body += "AD DOMAIN : {}\r\n".format(AD_DOMAIN)
  174. body += "Syslog/NetFlow: {}\r\n\r\n".format(SYSLOG)
  175. body += "vCenter is {}. You MUST use the web client. Your AD credentials above will work there. VMs that don't require an OVA have been pre-created, but require installation and configuration. If you use an OVA, you will need to deploy it yourself.\r\n\r\n".format(
  176. VCENTER
  177. )
  178. body += "Your VM details are as follows. DNS records have been pre-created for the VM name (i.e., hostname) below:\r\n\r\n"
  179. created = {}
  180. for vm in vms:
  181. iso_ds = ISO_DS
  182. cluster = DEFAULT_CLUSTER
  183. if vm["dc"] in HX_DCs:
  184. if vm["dc"].endswith("2"):
  185. iso_ds = ISO_DS_HX2
  186. else:
  187. iso_ds = ISO_DS_HX1
  188. cluster = vm["dc"]
  189. is_ova = False
  190. if vm["is_ova"].lower() == "true" or vm["is_ova"].lower() == "yes":
  191. is_ova = True
  192. ostype = None
  193. for ostypes in OSTYPE_LIST:
  194. if re.search(ostypes[0], vm["os"]):
  195. ostype = ostypes[1]
  196. break
  197. if not is_ova and ostype is None:
  198. print("WARNING: Did not find OS type for {}".format(vm["os"]))
  199. continue
  200. if not is_ova and vm["vlan"] != "" and vm["name"] not in created:
  201. print("===Adding VM for {}===".format(vm["name"]))
  202. mem = vm["mem"] * 1024
  203. scsi = "lsilogic"
  204. if re.search(r"^win", ostype):
  205. scsi = "lsilogicsas"
  206. os.chdir(ANSIBLE_PATH)
  207. command = [
  208. "ansible-playbook",
  209. "-i",
  210. "inventory/hosts",
  211. "-e",
  212. "vmware_cluster='{}'".format(cluster),
  213. "-e",
  214. "vmware_datacenter='{}'".format(DATACENTER),
  215. "-e",
  216. "guest_id={}".format(ostype),
  217. "-e",
  218. "guest_name={}".format(vm["name"]),
  219. "-e",
  220. "guest_size={}".format(vm["disk"]),
  221. "-e",
  222. "guest_mem={}".format(mem),
  223. "-e",
  224. "guest_cpu={}".format(vm["cpu"]),
  225. "-e",
  226. "guest_datastore={}".format(DC_MAP[vm["dc"]]),
  227. "-e",
  228. "guest_network='{}'".format(vm["vlan"]),
  229. "-e",
  230. "guest_scsi={}".format(scsi),
  231. "-e",
  232. "ansible_python_interpreter={}".format(sys.executable),
  233. "add-vm-playbook.yml",
  234. ]
  235. p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  236. output = ""
  237. for c in iter(lambda: p.stdout.read(1), b""):
  238. output += c.decode("utf-8")
  239. p.wait()
  240. rc = p.returncode
  241. if rc != 0:
  242. print("\n\n***ERROR: Failed to add VM {}\n{}!".format(vm["name"], output))
  243. continue
  244. print("===DONE===")
  245. if vm["name"] not in created:
  246. print("===Adding DNS record for {} ==> {}===".format(vm["name"], vm["ip"]))
  247. os.chdir(UPDATE_DNS_PATH)
  248. command = ["{}/update_dns.py".format(UPDATE_DNS_PATH), "--ip", vm["ip"], "--host", vm["name"]]
  249. p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  250. output = ""
  251. for c in iter(lambda: p.stdout.read(1), b""):
  252. output += c.decode("utf-8")
  253. p.wait()
  254. rc = p.returncode
  255. if rc != 0:
  256. print("\n\n***ERROR: Failed to add DNS record!\n{}".format(output))
  257. continue
  258. print("===DONE===")
  259. octets = vm["ip"].split(".")
  260. body += '{} : {} (v6: {}{}) (Network: {}, Subnet: {}, GW: {}, v6 Prefix: {}/64, v6 GW: {}) : Deploy to the {} datastore in the "{}" cluster.\r\n\r\nFor this VM upload ISOs to the {} datastore. There is an "ISOs" folder there already.\r\n\r\n'.format(
  261. vm["name"],
  262. vm["ip"],
  263. NETWORK_MAP[vm["vlan"]]["prefix"],
  264. format(int(octets[3]), "x"),
  265. vm["vlan"],
  266. NETWORK_MAP[vm["vlan"]]["subnet"],
  267. NETWORK_MAP[vm["vlan"]]["gw"],
  268. NETWORK_MAP[vm["vlan"]]["prefix"],
  269. NETWORK_MAP[vm["vlan"]]["gw6"],
  270. DC_MAP[vm["dc"]],
  271. cluster,
  272. iso_ds,
  273. )
  274. created[vm["name"]] = True
  275. body += "Let us know via Webex Teams if you need any other details.\r\n\r\n"
  276. body += "Joe, Kris and Anthony\r\n\r\n"
  277. subject = "Cisco Live Europe {} Data Centre Access Info".format(CISCOLIVE_YEAR)
  278. smtp = smtplib.SMTP(SMTP_SERVER)
  279. msg = EmailMessage()
  280. msg.set_content(body)
  281. msg["Subject"] = subject
  282. msg["From"] = FROM
  283. msg["To"] = user
  284. msg["Cc"] = CC + "," + FROM
  285. smtp.send_message(msg)
  286. smtp.quit()
  287. if __name__ == "__main__":
  288. main()