Skip to content

Instantly share code, notes, and snippets.

@jctanner
Created June 27, 2018 18:22
Show Gist options
  • Save jctanner/8179c084087bc10e03524236e1f5cabd to your computer and use it in GitHub Desktop.
Save jctanner/8179c084087bc10e03524236e1f5cabd to your computer and use it in GitHub Desktop.
ansible esxi_clone module
#!/usr/bin/python
# https://kb.vmware.com/s/article/1541 -- UUIDs
# https://kb.vmware.com/s/article/1000936
# http://www.vmwareblog.org/clone-vms-vmware-vcenter-unavailable/
# https://tylermade.net/2017/01/31/cloning-vms-in-vmware-vsphere-esxi-without-vcenter-the-right-way/
# https://nchrissos.wordpress.com/2014/03/05/clone-a-vmwares-vm-without-vcenter-in-esxi-5-x-by-commands-the-official-way/
import os
import paramiko
from ansible.module_utils.basic import AnsibleModule
class ParamikoHelper(object):
def __init__(self, hostname, port, username, password):
self.hostname = hostname
self.port = port
self.username = username
self.password = password
self.client = paramiko.SSHClient()
self.client.load_system_host_keys()
self.client.connect(hostname, port, username, password)
def run_command(self, command):
(stdin, stdout, stderr) = self.client.exec_command(command)
rc = stdout.channel.recv_exit_status()
stdout = stdout.read()
stderr = stderr.read()
return (rc, stdout, stderr)
class EsxiHelper(object):
def __init__(self, hostname, port, username, password):
self.hostname = hostname
self.port = port
self.username = username
self.password = password
self.ph = ParamikoHelper(hostname, port, username, password)
def vm_exists(self, name):
vms = self.get_vms()
for vm in vms:
if vm.Name == name:
return True
return False
def get_vms(self):
vms = []
command = 'vim-cmd vmsvc/getallvms'
(rc, so, se) = self.ph.run_command(command)
if rc != 0:
raise Exception("Failed getting VMs")
# ['Vmid Name File Guest OS Version Annotation',
# '16 ansible_dev [WD2TB] ansible_dev/ansible_dev.vmx centos7_64Guest vmx-13',
lines = so.split('\n')
#keys = [x.strip() for x in lines[0].split()]
keys = ['Vmid', 'Name', 'File', 'Guest_OS', 'Version', 'Annotation']
for line in lines[1:]:
if not line.strip():
continue
vm = {}
parts = line.split(' ')
parts = [x.strip() for x in parts if x.strip()]
for idk,key in enumerate(keys):
try:
vm[key] = parts[idk]
except Exception as e:
#print(e)
#import epdb; epdb.st()
pass
#vms.append(vm)
_vm = EsxiVirtualMachine(vm)
vms.append(_vm)
#import epdb; epdb.st()
return vms
def clone_vm(self, vm, newname, thin=True, datastore=None, cpus=None, memory=None):
if not isinstance(vm, EsxiVirtualMachine):
vms = self.get_vms()
_vm = [x for x in vms if x.Name == vm]
vm = _vm[0]
srcdir = self.get_vm_dspath(vm)
srcvmdks = self.get_vm_vmdkpaths(vm)
dstdir = os.path.join(os.path.dirname(srcdir), newname)
if not self.path_exists(dstdir):
self.mkdir(dstdir)
vmdkmap = {}
for srcvmdk in srcvmdks:
bn = os.path.basename(srcvmdk)
bn = bn.replace(vm.Name, newname)
dstvmdk = os.path.join(dstdir, bn)
vmdkmap[srcvmdk] = dstvmdk
vmx = self.read_vmx(vm)
dstvmx = self.edit_vmx(vmx, vm.Name, newname, diskmap=vmdkmap, cpus=cpus, memory=memory)
dstvmx_filename = os.path.join(dstdir, '%s.vmx' % newname)
self.write_vmx(dstvmx_filename, dstvmx)
#import epdb; epdb.st()
for k,v in vmdkmap.items():
self.clone_vmdk(k, v)
#import epdb; epdb.st()
self.register_vm(dstvmx_filename)
def register_vm(self, vmxfilepath):
# vim-cmd solo/registervm $(pwd)/redmine/redmine.vmx
command = 'vim-cmd solo/registervm %s' % vmxfilepath
(rc, so, se) = self.ph.run_command(command)
def edit_vmx(self, vmxdata, srcname, dstname, diskmap=None, cpus=None, memory=None):
lines = vmxdata.split('\n')
cpuset = False
memset = False
for idl,line in enumerate(lines):
newline = line[:]
val = newline.split('=')[-1]
val = val.replace('"', '')
val = val.strip()
if newline.startswith('displayName'):
newline = 'displayName = "%s"' % dstname
if newline.startswith('uuid.bios'):
newline = ''
if newline.startswith('uuid.location'):
newline = ''
if newline.startswith('vc.uuid'):
newline = ''
if newline.startswith('nvram ='):
newline = newline.replace(srcname, dstname)
if '.vmdk' in newline:
thiskey = None
for key in diskmap.keys():
if os.path.basename(key) == val:
thiskey = key
if thiskey:
newbn = os.path.basename(diskmap[thiskey])
newline = newline.replace(val, newbn)
if newline.startswith('numvcpus') and cpus:
newline = 'numvcpus = "%s"' % cpus
cpuset = True
if newline.startswith('memSize') and memory:
newline = 'memSize = "%s"' % memory
memset = True
lines[idl] = newline[:]
lines.append('uuid.action = "change"')
if cpus and not cpuset:
lines.append('numvcpus = "%s"' % cpus)
if memory and not memset:
lines.append('memSize = "%s"' % memory)
#import epdb; epdb.st()
lines = [x for x in lines if x]
return '\n'.join(lines)
def clone_vmdk(self, src, dst, thin=False):
# vmkfstools -i centos-7.5-x86_64/centos-7.5-x86_64.vmdk redmine/redmine.vmdk -d thin
command = 'vmkfstools -i %s %s' % (src, dst)
if thin:
command += ' -d thin'
print(command)
(rc, so, se) = self.ph.run_command(command)
if rc != 0:
print(se)
raise Exception('failed to clone vmdk')
def path_exists(self, path):
command = 'ls %s' % path
(rc, so, se) = self.ph.run_command(command)
if rc == 0:
return True
return False
def mkdir(self, path):
command = 'mkdir -p %s' % path
(rc, so, se) = self.ph.run_command(command)
if rc != 0:
raise Exception('failed to make directory')
def read_vmx(self, vm):
vmxfile = self.get_vm_vmxpath(vm)
command = 'cat %s' % vmxfile
(rc, so, se) = self.ph.run_command(command)
#import epdb; epdb.st()
return so
def write_vmx(self, filepath, data):
lines = data.split('\n')
for idl,line in enumerate(lines):
if idl == 0:
op = '>'
else:
op = '>>'
command = "echo '%s' %s %s" % (line, op, filepath)
(rc, so, se) = self.ph.run_command(command)
def get_vm_vmdkpaths(self, vm, ext='.vmdk'):
dspath = self.get_vm_dspath(vm)
command = 'ls %s/' % dspath
(rc, so, se) = self.ph.run_command(command)
vmdks = [x.strip() for x in so.split('\n') if x.strip()]
vmdks = [x for x in vmdks if x.endswith(ext)]
vmdks = [x for x in vmdks if not x.endswith('-flat.vmdk')]
vmdks = [os.path.join(dspath, x) for x in vmdks]
#import epdb; epdb.st()
return vmdks
def get_vm_vmxpath(self, vm):
vmxs = self.get_vm_vmdkpaths(vm, ext='.vmx')
return vmxs[0]
def get_vm_dspath(self, vm):
# vm.File == [WD2TB] ansible_dev/ansible_dev.vmx
dsname = vm.File[:vm.File.index(']')].replace('[', '')
dspath = os.path.join('/vmfs/volumes', dsname)
vmxpath = vm.File.split(None, 1)[-1]
vmxdir = vmxpath.split('/', 1)[0]
dspath = os.path.join(dspath, vmxdir)
#import epdb; epdb.st()
return dspath
class EsxiVirtualMachine(object):
Vmid = None
Name = None
File = None
Guest_OS = None
Version = None
Annotation = None
def __init__(self, vmdata):
for k, v in vmdata.items():
setattr(self, k, v)
def __repr__(self):
name = 'VirtualMachine'
name += ' '
name += self.Vmid
name += ' '
name += self.Name
return name
def main():
module = AnsibleModule(
argument_spec=dict(
hostname=dict(type='str', required=True),
port=dict(type='int', default=22),
username=dict(type='str', required=True),
password=dict(type='str', required=True),
template=dict(type='str', required=True),
name=dict(type='str', required=True),
cpus=dict(type='int', required=True),
memory=dict(type='int', required=True),
),
supports_check_mode=True,
)
esxi = EsxiHelper(
module.params['hostname'],
module.params['port'],
module.params['username'],
module.params['password']
)
changed = False
if not esxi.vm_exists(module.params['name']):
changed = True
if not module.check_mode:
esxi.clone_vm(
module.params['template'],
module.params['name'],
module.params['cpus'],
module.params['memory']
)
module.exit_json(changed=changed)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment