notify_dc_users.py 11 KB

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