#!/usr/bin/python import argparse # Argument parser import re # For regex import paramiko # For remote ssh from esxi_vm_functions import * # Defaults and Variable setup ConfigData = setup_config() NAME = "" LOG = ConfigData['LOG'] isDryRun = ConfigData['isDryRun'] isVerbose = ConfigData['isVerbose'] isSummary = ConfigData['isSummary'] HOST = ConfigData['HOST'] PORT = ConfigData['PORT'] USER = ConfigData['USER'] PASSWORD = ConfigData['PASSWORD'] KEY = ConfigData['KEY'] CPU = ConfigData['CPU'] MEM = ConfigData['MEM'] HDISK = int(ConfigData['HDISK']) DISKFORMAT = ConfigData['DISKFORMAT'] VIRTDEV = ConfigData['VIRTDEV'] STORE = ConfigData['STORE'] NET = ConfigData['NET'] ISO = ConfigData['ISO'] GUESTOS = ConfigData['GUESTOS'] VMXOPTS = ConfigData['VMXOPTS'] ErrorMessages = "" MAC = "" GeneratedMAC = "" ISOfound = False CheckHasErrors = False LeastUsedDS = "" DSPATH = "" DSSTORE = "" FullPathExists = False # # Process Arguments # parser = argparse.ArgumentParser(description='ESXi Create VM utility.') parser.add_argument('-d', '--dry', dest='isDryRunarg', action='store_true', help="Enable Dry Run mode (" + str(isDryRun) + ")") parser.add_argument("-H", "--Host", dest='HOST', type=str, help="ESXi Host/IP (" + str(HOST) + ")") parser.add_argument("-T", "--Port", dest='PORT', type=int, help="ESXi Port number (" + str(PORT) + ")") parser.add_argument("-U", "--User", dest='USER', type=str, help="ESXi Host username (" + str(USER) + ")") parser.add_argument("-P", "--Password", dest='PASSWORD', type=str, help="ESXi Host password (*****)") parser.add_argument("-K", "--Key", dest='KEY', type=str, help="ESXi Host connection key (path to private key)") parser.add_argument("-n", "--name", dest='NAME', type=str, help="VM name") parser.add_argument("-c", "--cpu", dest='CPU', type=int, help="Number of vCPUS (" + str(CPU) + ")") parser.add_argument("-m", "--mem", type=int, help="Memory in GB (" + str(MEM) + ")") parser.add_argument("-v", "--vdisk", dest='HDISK', type=str, help="Size of virt hdisk (" + str(HDISK) + ")") parser.add_argument("-i", "--iso", dest='ISO', type=str, help="CDROM ISO Path | None (" + str(ISO) + ")") parser.add_argument("-N", "--net", dest='NET', type=str, help="Network Interface | None (" + str(NET) + ")") parser.add_argument("-M", "--mac", dest='MAC', type=str, help="MAC address") parser.add_argument("-S", "--store", dest='STORE', type=str, help="vmfs Store | LeastUsed (" + str(STORE) + ")") parser.add_argument("-g", "--guestos", dest='GUESTOS', type=str, help="Guest OS. (" + str(GUESTOS) + ")") parser.add_argument("-o", "--options", dest='VMXOPTS', type=str, default='NIL', help="Comma list of VMX Options.") parser.add_argument('-V', '--verbose', dest='isVerbosearg', action='store_true', help="Enable Verbose mode (" + str(isVerbose) + ")") parser.add_argument('--summary', dest='isSummaryarg', action='store_true', help="Display Summary (" + str(isSummary) + ")") parser.add_argument("-u", "--updateDefaults", dest='UPDATE', action='store_true', help="Update Default VM settings stored in ~/.esxi-vm.yml") args = parser.parse_args() if args.isDryRunarg: isDryRun = True if args.isVerbosearg: isVerbose = True if args.isSummaryarg: isSummary = True if args.HOST: HOST = args.HOST if args.PORT: PORT = args.PORT if args.USER: USER = args.USER if args.PASSWORD: PASSWORD = args.PASSWORD if args.KEY: KEY = args.KEY if args.NAME: NAME = args.NAME if args.CPU: CPU = int(args.CPU) if args.mem: MEM = int(args.mem) if args.HDISK: HDISK = int(args.HDISK) if args.ISO: ISO = args.ISO if args.NET: NET = args.NET if args.MAC: MAC = args.MAC if args.STORE: STORE = args.STORE if STORE == "": STORE = "LeastUsed" if args.GUESTOS: GUESTOS = args.GUESTOS if args.VMXOPTS == '' and VMXOPTS != '': VMXOPTS = '' if args.VMXOPTS and args.VMXOPTS != 'NIL': VMXOPTS = args.VMXOPTS.split(",") if args.UPDATE: print("Saving new Defaults to ~/.esxi-vm.yml") ConfigData['isDryRun'] = isDryRun ConfigData['isVerbose'] = isVerbose ConfigData['isSummary'] = isSummary ConfigData['HOST'] = HOST ConfigData['PORT'] = PORT ConfigData['USER'] = USER ConfigData['PASSWORD'] = PASSWORD ConfigData['KEY'] = KEY ConfigData['CPU'] = CPU ConfigData['MEM'] = MEM ConfigData['HDISK'] = HDISK ConfigData['DISKFORMAT'] = DISKFORMAT ConfigData['VIRTDEV'] = VIRTDEV ConfigData['STORE'] = STORE ConfigData['NET'] = NET ConfigData['ISO'] = ISO ConfigData['GUESTOS'] = GUESTOS ConfigData['VMXOPTS'] = VMXOPTS save_config(ConfigData) if NAME == "": sys.exit(0) # # main() # LogOutput = '{' LogOutput += '"datetime":"{}",'.format(the_current_date_time()) if NAME == "": print("ERROR: Missing required option --name") sys.exit(1) try: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(HOST, port=PORT, username=USER, password=PASSWORD, key_filename=KEY) (stdin, stdout, stderr) = ssh.exec_command("esxcli system version get |grep Version") type(stdin) if re.match("Version", str(stdout.readlines())) is not None: print("Unable to determine if this is a ESXi Host: {}, port: {}, username: {}".format(HOST, PORT, USER)) sys.exit(1) except Exception as e: print("The Error is {}".format(e)) print("Unable to access ESXi Host: {}, port: {}, username: {}".format(HOST, PORT, USER)) sys.exit(1) try: (stdin, stdout, stderr) = \ ssh.exec_command("esxcli storage filesystem list |grep '/vmfs/volumes/.*true VMFS' |sort -nk7") type(stdin) VOLUMES = {} for line in stdout.readlines(): splitLine = line.split() VOLUMES[splitLine[0]] = splitLine[1] LeastUsedDS = splitLine[1] except Exception as e: print("The Error is {}".format(e)) sys.exit(1) if STORE == "LeastUsed": STORE = LeastUsedDS try: (stdin, stdout, stderr) = \ ssh.exec_command("esxcli network vswitch standard list|grep Portgroups|sed 's/^ Portgroups: //g'") type(stdin) VMNICS = [] for line in stdout.readlines(): splitLine = re.split('[,\n]', line) VMNICS.append(splitLine[0]) except Exception as e: print("The Error is {}".format(e)) sys.exit(1) MACarg = MAC if MAC != "": MACregex = '^([a-fA-F0-9]{2}[:|\-]){5}[a-fA-F0-9]{2}$' if re.compile(MACregex).search(MAC): # Full MAC found. OK MAC = MAC.replace("-", ":") elif re.compile(MACregex).search("00:50:56:" + MAC): MAC = "00:50:56:" + MAC.replace("-", ":") else: print("ERROR: {} Invalid MAC address.".format(MAC)) ErrorMessages += " " + MAC + " Invalid MAC address." CheckHasErrors = True ISOarg = ISO if ISO == "None": ISO = "" if ISO != "": try: # If ISO has no "/", try to find the ISO if not re.match('/', ISO): (stdin, stdout, stderr) = \ ssh.exec_command("find /vmfs/volumes/ -type f -name " + ISO + " -exec sh -c 'echo $1; kill $PPID' sh {} 2>/dev/null \;") type(stdin) FoundISOPath = str(stdout.readlines()[0]).strip('\n') if isVerbose: print("FoundISOPath: {}".format(FoundISOPath)) ISO = str(FoundISOPath) (stdin, stdout, stderr) = ssh.exec_command("ls " + str(ISO)) type(stdin) if stdout.readlines() and not stderr.readlines(): ISOfound = True except Exception as e: print("The Error is {}".format(e)) sys.exit(1) VMID = -1 try: (stdin, stdout, stderr) = ssh.exec_command("vim-cmd vmsvc/getallvms") type(stdin) for line in stdout.readlines(): splitLine = line.split() if NAME == splitLine[1]: VMID = splitLine[0] print("ERROR: VM {} already exists.".format(NAME)) ErrorMessages += " VM " + NAME + " already exists." CheckHasErrors = True except Exception as e: print("The Error is {}".format(e)) sys.exit(1) # Check CPU if CPU < 1 or CPU > 128: print("{} CPU out of range. [1-128].".format(CPU)) ErrorMessages += " {} CPU out of range. [1-128].".format(CPU) CheckHasErrors = True # Check MEM if MEM < 1 or MEM > 4080: print("{} GB Memory out of range. [1-4080].".format(MEM)) ErrorMessages += " {} GB Memory out of range. [1-4080].".format(MEM) CheckHasErrors = True # Check HDISK if HDISK < 1 or HDISK > 63488: print("Virtual Disk size {} GB out of range. [1-63488].".format(HDISK)) ErrorMessages += " Virtual Disk size {} GB out of range. [1-63488].".format(HDISK) CheckHasErrors = True # Convert STORE to path and visa-versa V = [] for Path in VOLUMES: V.append(VOLUMES[Path]) if STORE == Path or STORE == VOLUMES[Path]: DSPATH = Path DSSTORE = VOLUMES[Path] if DSSTORE not in V: print("ERROR: Disk Storage {} doesn't exist. ".format(STORE)) print(" Available Disk Stores: {}".format([str(item) for item in V])) print(" LeastUsed Disk Store : {}".format(LeastUsedDS)) ErrorMessages += " Disk Storage " + STORE + " doesn't exist. " CheckHasErrors = True # Check NIC (NIC record) if (NET not in VMNICS) and (NET != "None"): print("ERROR: Virtual NIC {} doesn't exist.".format(NET)) print(" Available VM NICs: {} or 'None'".format([str(item) for item in VMNICS])) ErrorMessages += " Virtual NIC {} doesn't exist.".format(NET) CheckHasErrors = True # Check ISO exists if ISO != "" and not ISOfound: print("ERROR: ISO {} not found. Use full path to ISO".format(ISO)) ErrorMessages += " ISO {} not found. Use full path to ISO".format(ISO) CheckHasErrors = True # Check if DSPATH/NAME already exists try: FullPath = DSPATH + "/" + NAME (stdin, stdout, stderr) = ssh.exec_command("ls -d " + FullPath) type(stdin) if stdout.readlines() and not stderr.readlines(): print("ERROR: Directory {} already exists.".format(FullPath)) ErrorMessages += " Directory {} already exists.".format(FullPath) CheckHasErrors = True except Exception as e: pass vmx = { 'config.version': '8', 'virtualHW.version': '8', 'vmci0.present': 'TRUE', 'displayName': NAME, 'floppy0.present': 'FALSE', 'numvcpus': CPU, 'scsi0.present': 'TRUE', 'scsi0.sharedBus': 'none', 'scsi0.virtualDev': 'pvscsi', 'memsize': str(MEM * 1024), 'scsi0:0.present': 'TRUE', 'scsi0:0.fileName': "{}.vmdk".format(NAME), 'scsi0:0.deviceType': 'scsi-hardDisk', 'pciBridge0.present': 'TRUE', 'pciBridge4.present': 'TRUE', 'pciBridge4.virtualDev': 'pcieRootPort', 'pciBridge4.functions': '8', 'pciBridge5.present': 'TRUE', 'pciBridge5.virtualDev': 'pcieRootPort', 'pciBridge5.functions': '8', 'pciBridge6.present': 'TRUE', 'pciBridge6.virtualDev': 'pcieRootPort', 'pciBridge6.functions': '8', 'pciBridge7.present': 'TRUE', 'pciBridge7.virtualDev': 'pcieRootPort', 'pciBridge7.functions': '8', 'guestOS': GUESTOS } if ISO == "": vmx.update({ 'ide1:0.present': 'TRUE', 'ide1:0.fileName': 'emptyBackingString', 'ide1:0.deviceType': 'atapi-cdrom', 'ide1:0.startConnected': 'FALSE', 'ide1:0.clientDevice': 'TRUE' }) else: vmx.update({ 'ide1:0.present': 'TRUE', 'ide1:0.fileName': ISO, 'ide1:0.deviceType': 'atapi-cdrom', 'ide1:0.startConnected': 'TRUE', }) if NET != "None": vmx.update({ 'ethernet0.virtualDev': 'vmxnet3', 'ethernet0.present': 'TRUE', 'ethernet0.networkName': NET }) if MAC == "": vmx.update({'ethernet0.addressType': 'generated'}) else: vmx.update({ 'ethernet0.addressType': 'static', 'ethernet0.address': MAC }) for VMXopt in VMXOPTS: try: k, v = VMXopt.split("=") except Exception: k = "" v = "" key = k.lstrip().strip() value = v.lstrip().strip() vmx[key] = value if isVerbose and VMXOPTS != '': print("VMX file:") for k, v in vmx.items(): print('{} = "{}"'.format(k, v)) MyVM = FullPath + "/" + NAME if CheckHasErrors: Result = "Errors" else: Result = "Success" if not isDryRun and not CheckHasErrors: try: if isVerbose: print("Create {}.vmx file".format(NAME)) (stdin, stdout, stderr) = ssh.exec_command("mkdir {}".format(FullPath)) type(stdin) for k, v in vmx.items(): (stdin, stdout, stderr) = ssh.exec_command("echo '{} = \"{}\"' >> {}.vmx".format(k, v, MyVM)) type(stdin) if isVerbose: print("Create {}.vmdk file".format(NAME)) (stdin, stdout, stderr) = \ ssh.exec_command("vmkfstools -c {}G -d {} {}.vmdk".format(HDISK, DISKFORMAT, MyVM)) type(stdin) if isVerbose: print("Register VM") (stdin, stdout, stderr) = ssh.exec_command("vim-cmd solo/registervm {}.vmx".format(MyVM)) type(stdin) VMID = int(stdout.readlines()[0]) if isVerbose: print("Power ON VM") (stdin, stdout, stderr) = ssh.exec_command("vim-cmd vmsvc/power.on {}".format(VMID)) type(stdin) if stderr.readlines(): print("Error Powering-on VM.") Result = "Fail" if NET != "None": (stdin, stdout, stderr) = ssh.exec_command( "grep -i 'ethernet0.*ddress = ' {}.vmx |tail -1|awk '{print $NF}'".format(MyVM)) type(stdin) GeneratedMAC = str(stdout.readlines()[0]).strip('\n"') except Exception as e: print("There was an error creating the VM.") ErrorMessages += " There was an error creating the VM." Result = "Fail" LogOutput += '"Host":"{}","Port":"{}","Name":"{}",'.format(HOST, PORT, NAME) LogOutput += '"CPU":"{}","Mem":"{}",'.format(CPU, MEM) LogOutput += '"Hdisk":"{}","DiskFormat":"{}","Virtual Device":"{}",'.format(HDISK, DISKFORMAT, VIRTDEV) LogOutput += '"Store":"{}","Store Used":"{}",'.format(STORE, DSPATH) LogOutput += '"Network":"{}",'.format(NET) LogOutput += '"ISO":"{}","ISO used":"{}",'.format(ISOarg, ISO) LogOutput += '"Guest OS":"{}",'.format(GUESTOS) LogOutput += '"MAC":"{}","MAC Used":"'.format(MACarg, GeneratedMAC) LogOutput += '"Dry Run":"{}","Verbose":"{}",'.format(isDryRun, isVerbose) if ErrorMessages != "": LogOutput += '"Error Message":"{}",'.format(ErrorMessages) LogOutput += '"Result":"{}","Completion Time":"{}"'.format(Result, the_current_date_time()) LogOutput += '}\n' try: with open(LOG, "a") as FD: FD.write(LogOutput) except Exception as e: print("Error writing to log file: {}".format(LOG)) if isSummary: if isDryRun: print("\nDry Run summary:") else: print("\nCreate VM Success:") if isVerbose: print("ESXi Host: {}".format(HOST)) print("ESXi Port: {}".format(PORT)) print("VM NAME: {}".format(NAME)) print("vCPU: {}".format(CPU)) print("Memory: {} GB".format(MEM)) print("VM Disk: {} GB".format(HDISK)) if isVerbose: print("Format: {}".format(DISKFORMAT)) print("DS Store: {}".format(DSSTORE)) print("Network: {}".format(NET)) if ISO: print("ISO: {}".format(ISO)) if isVerbose: print("Guest OS: {}".format(GUESTOS)) print("MAC: {}".format(GeneratedMAC)) else: pass if CheckHasErrors: if isDryRun: print("Dry Run: Failed.") sys.exit(1) else: if isDryRun: print("Dry Run: Success.") else: print(GeneratedMAC) sys.exit(0)