@ -23,39 +23,40 @@ ANSIBLE_METADATA = {'metadata_version': '1.0',
' supported_by ' : ' community ' }
DOCUMENTATION = '''
DOCUMENTATION = r '''
- - -
module : vmware_guest
short_description : Manages virtual machines in v c enter
short_description : Manages virtual machines in v C enter
description :
- Create new virtual machines ( from templates or not )
- Power on / power off / restart a virtual machine
- Modify , rename or remove a virtual machine
version_added : 2.2
- Create new virtual machines ( from templates or not ) .
- Power on / power off / restart a virtual machine .
- Modify , rename or remove a virtual machine .
version_added : ' 2.2 '
author :
- James Tanner ( @jctanner ) < tanner . jc @gmail.com >
- Loic Blot ( @nerzhul ) < loic . blot @unix - experience . fr >
- James Tanner ( @jctanner ) < tanner . jc @gmail.com >
- Loic Blot ( @nerzhul ) < loic . blot @unix - experience . fr >
notes :
- Tested on vSphere 5.5 and 6.0
- Tested on vSphere 5.5 and 6.0
requirements :
- " python >= 2.6 "
- PyVmomi
- python > = 2.6
- PyVmomi
options :
state :
description :
- What state should the virtual machine be in ?
- If C ( state ) is set to C ( present ) and VM exists , ensure the VM configuration conforms to task arguments
required : True
choices : [ ' present ' , ' absent ' , ' poweredon ' , ' poweredoff ' , ' restarted ' , ' suspended ' , ' shutdownguest ' , ' rebootguest ' ]
- If C ( state ) is set to C ( present ) and VM exists , ensure the VM configuration conforms to task arguments .
required : yes
choices : [ ' present ' , ' absent ' , ' poweredon ' , ' poweredoff ' , ' restarted ' , ' suspended ' , ' shutdownguest ' , ' rebootguest ' ]
name :
description :
- Name of the VM to work with
required : True
- Name of the VM to work with .
- VM names in vCenter are not necessarily unique , which may be problematic , see C ( name_match ) .
required : yes
name_match :
description :
- If multiple VMs matching the name , use the first or last found
- If multiple VMs matching the name , use the first or last found .
default : ' first '
choices : [ ' first ' , ' last ' ]
choices : [ ' first ' , ' last ' ]
uuid :
description :
- UUID of the instance to manage if known , this is VMware ' s unique identifier.
@ -67,124 +68,136 @@ options:
- If the VM exists already this setting will be ignored .
is_template :
description :
- Flag the instance as a template
default : False
version_added : " 2.3 "
- Flag the instance as a template .
default : ' no '
type : bool
version_added : ' 2.3 '
folder :
description :
- Destination folder , absolute path to find an existing guest or create the new guest
- Destination folder , absolute path to find an existing guest or create the new guest .
default : /
hardware :
description :
- " Manage some VM hardware attributes. "
- " Valid attributes are: memory_mb, num_cpus and scsi "
- " scsi: Valid values are buslogic, lsilogic, lsilogicsas and paravirtual (default) "
- Manage some VM hardware attributes .
- ' Valid attributes are: '
- ' - C(memory_mb) (integer): Amount of memory in MB. '
- ' - C(num_cpus) (integer): Number of CPUs. '
- ' - C(scsi) (string): Valid values are C(buslogic), C(lsilogic), C(lsilogicsas) and C(paravirtual) (default). '
guest_id :
description :
- " Set the guest ID (Debian, RHEL, Windows...) "
- " This field is required when creating a VM "
- Set the guest ID ( Debian , RHEL , Windows . . . ) .
- This field is required when creating a VM .
- >
Valid values are referenced here :
https : / / www . vmware . com / support / developer / converter - sdk / conv55_apireference / vim . vm . GuestOsDescriptor . GuestOsIdentifier . html
version_added : " 2.3 "
version_added : ' 2.3 '
disk :
description :
- " A list of disks to add "
- " Valid attributes are: size_[tb,gb,mb,kb], type, datastore and autoselect_datastore "
- " type: Valid value is thin (default: None) "
- " datastore: Datastore to use for the disk. If autoselect_datastore is True, filter datastore selection. "
- " autoselect_datastore (bool): select the less used datastore. "
- A list of disks to add .
- ' Valid attributes are: '
- ' - C(size_[tb,gb,mb,kb]) (integer): Disk storage size in specified unit. '
- ' - C(type) (string): Valid value is C(thin) (default: None). '
- ' - C(datastore) (string): Datastore to use for the disk. If C(autoselect_datastore) is enabled, filter datastore selection. '
- ' - C(autoselect_datastore) (bool): select the less used datastore. '
resource_pool :
description :
- Affect machine to the given resource pool
- Resource pool should be child of the selected host parent
default : None
version_added : " 2.3 "
- Affect machine to the given resource pool .
- Resource pool should be child of the selected host parent .
version_added : ' 2.3 '
wait_for_ip_address :
description :
- Wait until vCenter detects an IP address for the VM
- This requires vmware - tools ( vmtoolsd ) to properly work after creation
default : False
- Wait until vCenter detects an IP address for the VM .
- This requires vmware - tools ( vmtoolsd ) to properly work after creation .
default : ' no '
type : bool
snapshot_src :
description :
- Name of an existing snapshot to use to create a clone of a VM .
default : None
version_added : " 2.4 "
version_added : ' 2.4 '
linked_clone :
description :
- Whether to create a Linked Clone from the snapshot specified
default : False
version_added : " 2.4 "
- Whether to create a Linked Clone from the snapshot specified .
default : ' no '
type : bool
version_added : ' 2.4 '
force :
description :
- Ignore warnings and complete the actions
- Ignore warnings and complete the actions .
default : ' no '
type : bool
datacenter :
description :
- Destination datacenter for the deploy operation
- Destination datacenter for the deploy operation .
default : ha - datacenter
cluster :
description :
- The cluster name where the VM will run .
version_added : " 2.3 "
version_added : ' 2.3 '
esxi_hostname :
description :
- The esx i hostname where the VM will run .
- The ESX i hostname where the VM will run .
annotation :
description :
- A note or annotation to include in the VM
version_added : " 2.3 "
- A note or annotation to include in the VM .
version_added : ' 2.3 '
customvalues :
description :
- Define a list of customvalues to set on VM .
- " A customvalue object takes 2 fields ' key ' and ' value ' . "
version_added : " 2.3 "
- A customvalue object takes 2 fields C ( key ) and C ( value ) .
version_added : ' 2.3 '
networks :
description :
- Network to use should include C ( name ) or C ( vlan ) entry
- Add an optional C ( ip ) and C ( netmask ) for network configuration
- Add an optional C ( gateway ) entry to configure a gateway
- Add an optional C ( mac ) entry to customize mac address
- Add an optional C ( dns_servers ) or C ( domain ) entry per interface ( Windows )
- Add an optional C ( device_type ) to configure the virtual NIC ( pcnet32 , vmxnet2 , vmxnet3 , e1000 , e1000e )
version_added : " 2.3 "
- A list of networks ( in the order of the NICs ) .
- ' One of the below parameters is required per entry: '
- ' - C(name) (string): Name of the portgroup for this interface. '
- ' - C(vlan) (integer): VLAN number for this interface. '
- ' Optional parameters per entry (used for virtual hardware): '
- ' - C(device_type) (string): Virtual network device (one of C(e1000), C(e1000e), C(pcnet32), C(vmxnet2), C(vmxnet3) (default), C(sriov)). '
- ' - C(mac) (string): Customize mac address. '
- ' Optional parameters per entry (used for OS customization): '
- ' - C(type) (string): Type of IP assignment (either C(dhcp) or C(static)). '
- ' - C(ip) (string): Static IP address (implies C(type: static)). '
- ' - C(netmask) (string): Static netmask required for C(ip). '
- ' - C(gateway) (string): Static gateway. '
- ' - C(dns_servers) (string): DNS servers for this network interface (Windows). '
- ' - C(domain) (string): Domain name for this network interface (Windows). '
version_added : ' 2.3 '
customization :
description :
- " Parameters to customize template "
- " Common parameters (Linux/Windows): "
- " C(dns_servers) (list): List of DNS servers to configure "
- " C(dns_suffix) (list): List of domain suffixes, aka DNS search path (default: C(domain) parameter) "
- " C(domain) (string): DNS domain name to use "
- " C(hostname) (string): Computer hostname (default: C(name) parameter) "
- " Parameters related to windows customization: "
- " C(autologon) (bool): Auto logon after VM customization (default: False) "
- " C(autologoncount) (int): Number of autologon after reboot (default: 1) "
- " C(domainadmin) (string): User used to join in AD domain (mandatory with joindomain) "
- " C(domainadminpassword) (string): Password used to join in AD domain (mandatory with joindomain) "
- " C(fullname) (string): Server owner name (default: Administrator) "
- " C(joindomain) (string): AD domain to join (Not compatible with C(joinworkgroup)) "
- " C(joinworkgroup) (string): Workgroup to join (Not compatible with C(joindomain), default: WORKGROUP)"
- " C(orgname) (string): Organisation name (default: ACME) "
- " C(password) (string): Local administrator password (mandatory) "
- " C(productid) (string): Product ID "
- " C(runonce) (list): List of commands to run at first user logon "
- " C(timezone) (int): Timezone (default: 85) See U(https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx) "
version_added : " 2.3 "
- Parameters for OS customization when cloning from template .
- ' Common parameters (Linux/Windows): '
- ' - C(dns_servers) (list): List of DNS servers to configure. '
- ' - C(dns_suffix) (list): List of domain suffixes, aka DNS search path (default: C(domain) parameter). '
- ' - C(domain) (string): DNS domain name to use. '
- ' - C(hostname) (string): Computer hostname (default: shorted C(name) parameter). '
- ' Parameters related to Windows customization: '
- ' - C(autologon) (bool): Auto logon after VM customization (default: False). '
- ' - C(autologoncount) (int): Number of autologon after reboot (default: 1). '
- ' - C(domainadmin) (string): User used to join in AD domain (mandatory with C(joindomain)). '
- ' - C(domainadminpassword) (string): Password used to join in AD domain (mandatory with C(joindomain)). '
- ' - C(fullname) (string): Server owner name (default: Administrator). '
- ' - C(joindomain) (string): AD domain to join (Not compatible with C(joinworkgroup)). '
- ' - C(joinworkgroup) (string): Workgroup to join (Not compatible with C(joindomain), default: WORKGROUP).'
- ' - C(orgname) (string): Organisation name (default: ACME). '
- ' - C(password) (string): Local administrator password. '
- ' - C(productid) (string): Product ID. '
- ' - C(runonce) (list): List of commands to run at first user logon. '
- ' - C(timezone) (int): Timezone (See U(https://msdn.microsoft.com/en-us/library/ms912391.aspx)). '
version_added : ' 2.3 '
extends_documentation_fragment : vmware . documentation
'''
EXAMPLES = '''
# Create a VM from a template
- name : create the VM
EXAMPLES = r '''
- name : Create a VM from a template
vmware_guest :
hostname : 192.0 .2 .44
username : administrator @vsphere.local
password : vmware
validate_certs : no
esxi_hostname : 192.0 .2 .117
datacenter : datacenter1
folder : testvms
folder : / testvms
name : testvm_2
state : poweredon
guest_id : centos64guest
template : template_el7
disk :
- size_gb : 10
type : thin
@ -195,16 +208,12 @@ EXAMPLES = '''
scsi : paravirtual
networks :
- name : VM Network
ip : 192.168 .1 .100
netmask : 255.255 .255 .0
mac : ' aa:bb:dd:aa:00:14 '
template : template_el7
mac : aa : bb : dd : aa : 00 : 14
wait_for_ip_address : yes
delegate_to : localhost
register : deploy
# Clone a VM from Template and customize
- name : Clone template and customize
- name : Clone a VM from Template and customize
vmware_guest :
hostname : 192.168 .1 .209
username : administrator @vsphere.local
@ -219,24 +228,25 @@ EXAMPLES = '''
ip : 192.168 .1 .100
netmask : 255.255 .255 .0
gateway : 192.168 .1 .1
mac : ' aa:bb:dd:aa:00:14 '
mac : aa : bb : dd : aa : 00 : 14
domain : my_domain
dns_servers :
- 192.168 .1 .1
- 192.168 .1 .2
- vlan : 1234
type : dhcp
customization :
autologon : True
autologon : yes
dns_servers :
- 192.168 .1 .1
- 192.168 .1 .2
domain : my_domain
password : new_vm_password
runonce :
- powershell . exe - ExecutionPolicy Unrestricted - File C : \Windows \Temp \Enable- WinRM . ps1 - ForceNewSSLCert
- powershell . exe - ExecutionPolicy Unrestricted - File C : \Windows \Temp \ConfigureRemotingForAnsible . ps1 - ForceNewSSLCert - EnableCredSSP
delegate_to : localhost
# Create a VM template
- name : create a VM template
- name : Create a VM template
vmware_guest :
hostname : 192.0 .2 .88
username : administrator @vsphere.local
@ -245,7 +255,7 @@ EXAMPLES = '''
datacenter : datacenter1
cluster : vmware_cluster_esx
resource_pool : highperformance_pool
folder : testvms
folder : / testvms
name : testvm_6
is_template : yes
guest_id : debian6_64Guest
@ -257,12 +267,11 @@ EXAMPLES = '''
memory_mb : 512
num_cpus : 1
scsi : lsilogic
wait_for_ip_address : yes
delegate_to : localhost
register : deploy
# Rename a VM (requires the VM' s uuid)
- vmware_guest :
- name : Rename a VM ( requires the VM ' s uuid)
vmware_guest :
hostname : 192.168 .1 .209
username : administrator @vsphere.local
password : vmware
@ -271,8 +280,8 @@ EXAMPLES = '''
state : present
delegate_to : localhost
# Remove a VM by uuid
- vmware_guest :
- name : Remove a VM by uuid
vmware_guest :
hostname : 192.168 .1 .209
username : administrator @vsphere.local
password : vmware
@ -281,23 +290,22 @@ EXAMPLES = '''
delegate_to : localhost
'''
RETURN = """
RETURN = r'''
instance :
description : metadata about the new virtualmachine
returned : always
type : dict
sample : None
"""
'''
import os
import time
# import module snippets
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . pycompat24 import get_exception
from ansible . module_utils . six import iteritems
from ansible . module_utils . urls import fetch_url
from ansible . module_utils . vmware import get_all_objs, connect_to_api, gather_vm_facts
from ansible . module_utils . vmware import connect_to_api, find_obj, gather_vm_facts, get_all_objs
try :
import json
@ -395,8 +403,7 @@ class PyVmomiDeviceHelper(object):
elif device_type == ' sriov ' :
nic . device = vim . vm . device . VirtualSriovEthernetCard ( )
else :
self . module . fail_json ( msg = " Invalid device_type ' %s ' for network %s " %
( device_type , device_infos [ ' name ' ] ) )
self . module . fail_json ( msg = ' Invalid device_type " %s " for network " %s " ' % ( device_type , device_infos [ ' name ' ] ) )
nic . device . wakeOnLanEnabled = True
nic . device . deviceInfo = vim . Description ( )
@ -425,19 +432,19 @@ class PyVmomiCache(object):
def get_network ( self , network ) :
if network not in self . networks :
self . networks [ network ] = get _obj( self . content , [ vim . Network ] , network )
self . networks [ network ] = find _obj( self . content , [ vim . Network ] , network )
return self . networks [ network ]
def get_cluster ( self , cluster ) :
if cluster not in self . clusters :
self . clusters [ cluster ] = get _obj( self . content , [ vim . ClusterComputeResource ] , cluster )
self . clusters [ cluster ] = find _obj( self . content , [ vim . ClusterComputeResource ] , cluster )
return self . clusters [ cluster ]
def get_esx_host ( self , host ) :
if host not in self . esx_hosts :
self . esx_hosts [ host ] = get _obj( self . content , [ vim . HostSystem ] , host )
self . esx_hosts [ host ] = find _obj( self . content , [ vim . HostSystem ] , host )
return self . esx_hosts [ host ]
@ -458,9 +465,6 @@ class PyVmomiHelper(object):
self . current_vm_obj = None
self . cache = PyVmomiCache ( self . content )
def should_deploy_from_template ( self ) :
return self . params . get ( ' template ' ) is not None
def getvm ( self , name = None , uuid = None , folder = None ) :
# https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.SearchIndex.html
@ -584,7 +588,7 @@ class PyVmomiHelper(object):
def configure_guestid ( self , vm_obj , vm_creation = False ) :
# guest_id is not required when using templates
if self . should_deploy_from_template( ) and self . params . get ( ' guest_id ' ) is None :
if self . params[ ' template ' ] and not self . params [ ' guest_id ' ] :
return
# guest_id is only mandatory on VM creation
@ -603,7 +607,7 @@ class PyVmomiHelper(object):
if vm_obj is None or self . configspec . numCPUs != vm_obj . config . hardware . numCPU :
self . change_detected = True
# num_cpu is mandatory for VM creation
elif vm_creation and not self . should_deploy_from_template( ) :
elif vm_creation and not self . params[ ' template ' ] :
self . module . fail_json ( msg = " hardware.num_cpus attribute is mandatory for VM creation " )
if ' memory_mb ' in self . params [ ' hardware ' ] :
@ -611,10 +615,9 @@ class PyVmomiHelper(object):
if vm_obj is None or self . configspec . memoryMB != vm_obj . config . hardware . memoryMB :
self . change_detected = True
# memory_mb is mandatory for VM creation
elif vm_creation and not self . should_deploy_from_template( ) :
elif vm_creation and not self . params[ ' template ' ] :
self . module . fail_json ( msg = " hardware.memory_mb attribute is mandatory for VM creation " )
def get_vm_network_interfaces ( self , vm = None ) :
if vm is None :
return [ ]
@ -639,11 +642,11 @@ class PyVmomiHelper(object):
network_devices = list ( )
for network in self . params [ ' networks ' ] :
if ' ip ' in network or ' netmask ' in network :
if ' ip ' not in network or not ' netmask ' in network :
if ' ip ' not in network or ' netmask ' not in network :
self . module . fail_json ( msg = " Both ' ip ' and ' netmask ' are required together. " )
if ' name ' in network :
if get _obj( self . content , [ vim . Network ] , network [ ' name ' ] ) is None :
if find _obj( self . content , [ vim . Network ] , network [ ' name ' ] ) is None :
self . module . fail_json ( msg = " Network ' %(name)s ' does not exists " % network )
elif ' vlan ' in network :
@ -677,7 +680,7 @@ class PyVmomiHelper(object):
network_devices [ key ] )
nic_change_detected = False
if key < len ( current_net_devices ) and ( vm_obj or self . should_deploy_from_template( ) ) :
if key < len ( current_net_devices ) and ( vm_obj or self . params[ ' template ' ] ) :
nic . operation = vim . vm . device . VirtualDeviceSpec . Operation . edit
# Changing mac address has no effect when editing interface
if ' mac ' in network_devices [ key ] and nic . device . macAddress != current_net_devices [ key ] . macAddress :
@ -692,7 +695,7 @@ class PyVmomiHelper(object):
if hasattr ( self . cache . get_network ( network_devices [ key ] [ ' name ' ] ) , ' portKeys ' ) :
# VDS switch
pg_obj = get _obj( self . content , [ vim . dvs . DistributedVirtualPortgroup ] , network_devices [ key ] [ ' name ' ] )
pg_obj = find _obj( self . content , [ vim . dvs . DistributedVirtualPortgroup ] , network_devices [ key ] [ ' name ' ] )
if ( nic . device . backing and
( nic . device . backing . port . portgroupKey != pg_obj . key or
@ -747,12 +750,22 @@ class PyVmomiHelper(object):
# Network settings
adaptermaps = [ ]
for network in self . params [ ' networks ' ] :
if ' ip ' in network and ' netmask ' in network :
guest_map = vim . vm . customization . AdapterMapping ( )
guest_map . adapter = vim . vm . customization . IPSettings ( )
if ' ip ' in network and ' netmask ' in network :
if ' type ' in network and network [ ' type ' ] != ' static ' :
self . module . fail_json ( msg = ' Static IP information provided for network " %(name)s " , but " type " is set to " %(type)s " . ' % network )
guest_map . adapter . ip = vim . vm . customization . FixedIp ( )
guest_map . adapter . ip . ipAddress = str ( network [ ' ip ' ] )
guest_map . adapter . subnetMask = str ( network [ ' netmask ' ] )
elif ' type ' in network and network [ ' type ' ] == ' static ' :
self . module . fail_json ( msg = ' Network " %(name)s " was set to type " %(type)s " , but " ip " and " netmask " are missing. ' % network )
elif ' type ' in network and network [ ' type ' ] == ' dhcp ' :
guest_map . adapter . ip = vim . vm . customization . DhcpIpGenerator ( )
else :
self . module . fail_json ( msg = ' Network " %(name)s " was set to unknown type " %(type)s " . ' % network )
if ' gateway ' in network :
guest_map . adapter . gateway = network [ ' gateway ' ]
@ -761,11 +774,12 @@ class PyVmomiHelper(object):
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.IPSettings.html
if ' domain ' in network :
guest_map . adapter . dnsDomain = network [ ' domain ' ]
elif self . params [ ' customization ' ] . get ( ' domain ' ) :
elif ' domain ' in self . params [ ' customization ' ] :
guest_map . adapter . dnsDomain = self . params [ ' customization ' ] [ ' domain ' ]
if ' dns_servers ' in network :
guest_map . adapter . dnsServerList = network [ ' dns_servers ' ]
elif self . params [ ' customization ' ] . get ( ' dns_servers ' ) :
elif ' dns_servers ' in self . params [ ' customization ' ] :
guest_map . adapter . dnsServerList = self . params [ ' customization ' ] [ ' dns_servers ' ]
adaptermaps . append ( guest_map )
@ -773,31 +787,43 @@ class PyVmomiHelper(object):
# Global DNS settings
globalip = vim . vm . customization . GlobalIPSettings ( )
if ' dns_servers ' in self . params [ ' customization ' ] :
globalip . dnsServerList = self . params [ ' customization ' ] . get ( ' dns_servers ' )
globalip . dnsServerList = self . params [ ' customization ' ] [ ' dns_servers ' ]
# TODO: Maybe list the different domains from the interfaces here by default ?
if ' dns_suffix ' in self . params [ ' customization ' ] or ' domain ' in self . params [ ' customization ' ] :
globalip . dnsSuffixList = self . params [ ' customization ' ] . get ( ' dns_suffix ' , self . params [ ' customization ' ] [ ' domain ' ] )
if ' dns_suffix ' in self . params [ ' customization ' ] :
globalip . dnsSuffixList = self . params [ ' customization ' ] [ ' dns_suffix ' ]
elif ' domain ' in self . params [ ' customization ' ] :
globalip . dnsSuffixList = self . params [ ' customization ' ] [ ' domain ' ]
if self . params [ ' guest_id ' ] :
guest_id = self . params [ ' guest_id ' ]
else :
guest_id = vm_obj . summary . config . guestId
# If I install a Windows use Sysp rep
# For windows guest OS, use SysP rep
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.Sysprep.html#field_detail
if ' win ' in guest_id :
ident = vim . vm . customization . Sysprep ( )
ident . userData = vim . vm . customization . UserData ( )
# Setting hostName, orgName and fullName is mandatory, so we set some default when missing
ident . userData . computerName = vim . vm . customization . FixedName ( )
ident . userData . computerName . name = str ( self . params [ ' customization ' ] . get ( ' hostname ' , self . params [ ' name ' ] ) )
ident . userData . computerName . name = str ( self . params [ ' customization ' ] . get ( ' hostname ' , self . params [ ' name ' ] . split ( ' . ' ) [ 0 ] ) )
ident . userData . fullName = str ( self . params [ ' customization ' ] . get ( ' fullname ' , ' Administrator ' ) )
ident . userData . orgName = str ( self . params [ ' customization ' ] . get ( ' orgname ' , ' ACME ' ) )
if ' productid ' in self . params [ ' customization ' ] :
ident . userData . productId = str ( self . params [ ' customization ' ] [ ' productid ' ] )
ident . guiUnattended = vim . vm . customization . GuiUnattended ( )
ident . guiUnattended . autoLogon = self . params [ ' customization ' ] . get ( ' autologon ' , False )
if ' autologon ' in self . params [ ' customization ' ] :
ident . guiUnattended . autoLogon = self . params [ ' customization ' ] [ ' autologon ' ]
ident . guiUnattended . autoLogonCount = self . params [ ' customization ' ] . get ( ' autologoncount ' , 1 )
ident . guiUnattended . timeZone = self . params [ ' customization ' ] . get ( ' timezone ' , 85 )
if ' timezone ' in self . params [ ' customization ' ] :
ident . guiUnattended . timeZone = self . params [ ' customization ' ] [ ' timezone ' ]
ident . identification = vim . vm . customization . Identification ( )
@ -805,38 +831,38 @@ class PyVmomiHelper(object):
ident . guiUnattended . password = vim . vm . customization . Password ( )
ident . guiUnattended . password . value = str ( self . params [ ' customization ' ] [ ' password ' ] )
ident . guiUnattended . password . plainText = True
else :
self . module . fail_json ( msg = " The ' customization ' section requires a ' password ' entry, which cannot be empty. " )
if ' productid ' in self . params [ ' customization ' ] :
ident . userData . orgName = str ( self . params [ ' customization ' ] [ ' productid ' ] )
if ' joindomain ' in self . params [ ' customization ' ] :
if ' domainadmin ' not in self . params [ ' customization ' ] or ' domainadminpassword ' not in self . params [ ' customization ' ] :
self . module . fail_json ( msg = " ' domainadmin ' and ' domainadminpassword ' entries are mandatory in ' customization ' section to use "
" joindomain feature " )
ident . identification . domainAdmin = str ( self . params [ ' customization ' ] . get ( ' domainadmin ' ) )
ident . identification . joinDomain = str ( self . params [ ' customization ' ] . get ( ' joindomain ' ) )
ident . identification . domainAdmin = str ( self . params [ ' customization ' ] [ ' domainadmin ' ] )
ident . identification . joinDomain = str ( self . params [ ' customization ' ] [ ' joindomain ' ] )
ident . identification . domainAdminPassword = vim . vm . customization . Password ( )
ident . identification . domainAdminPassword . value = str ( self . params [ ' customization ' ] . get ( ' domainadminpassword ' ) )
ident . identification . domainAdminPassword . value = str ( self . params [ ' customization ' ] [ ' domainadminpassword ' ] )
ident . identification . domainAdminPassword . plainText = True
elif ' joinworkgroup ' in self . params [ ' customization ' ] :
ident . identification . joinWorkgroup = str ( self . params [ ' customization ' ] . get ( ' joinworkgroup ' ) )
ident . identification . joinWorkgroup = str ( self . params [ ' customization ' ] [ ' joinworkgroup ' ] )
if ' runonce ' in self . params [ ' customization ' ] :
ident . guiRunOnce = vim . vm . customization . GuiRunOnce ( )
ident . guiRunOnce . commandList = self . params [ ' customization ' ] [ ' runonce ' ]
else :
# Else use LinuxPrep
# FIXME: We have no clue whether this non-Windows OS is actually Linux, hence it might fail !
# For Linux guest OS, use LinuxPrep
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.LinuxPrep.html
ident = vim . vm . customization . LinuxPrep ( )
# TODO: Maybe add domain from interface if missing ?
if ' domain ' in self . params [ ' customization ' ] :
ident . domain = str ( self . params [ ' customization ' ] . get ( ' domain ' ) )
ident . domain = str ( self . params [ ' customization ' ] [ ' domain ' ] )
ident . hostName = vim . vm . customization . FixedName ( )
ident . hostName . name = str ( self . params [ ' customization ' ] . get ( ' hostname ' , self . params [ ' name ' ] ) )
ident . hostName . name = str ( self . params [ ' customization ' ] . get ( ' hostname ' , self . params [ ' name ' ] . split ( ' . ' ) [ 0 ] ) )
self . customspec = vim . vm . customization . Specification ( )
self . customspec . nicSettingMap = adaptermaps
@ -941,7 +967,7 @@ class PyVmomiHelper(object):
# VMWare doesn't allow to reduce disk sizes
if kb < diskspec . device . capacityInKB :
self . module . fail_json (
msg = " Given disk size is less er than found (%d < %d ). Reducing disks is not allowed. " %
msg = " Given disk size is smal ler than found (%d < %d ). Reducing disks is not allowed. " %
( kb , diskspec . device . capacityInKB ) )
if kb != diskspec . device . capacityInKB or disk_modified :
@ -955,14 +981,16 @@ class PyVmomiHelper(object):
if self . params [ ' cluster ' ] :
cluster = self . cache . get_cluster ( self . params [ ' cluster ' ] )
if not cluster :
self . module . fail_json ( msg = " Failed to find a cluster named %(cluster)s " % self . params )
self . module . fail_json ( msg = ' Failed to find cluster " %(cluster)s " ' % self . params )
hostsystems = [ x for x in cluster . host ]
if not hostsystems :
self . module . fail_json ( msg = ' No hosts found in cluster " %(cluster)s . Maybe you lack the right privileges ? " ' % self . params )
# TODO: add a policy to select host
hostsystem = hostsystems [ 0 ]
else :
hostsystem = self . cache . get_esx_host ( self . params [ ' esxi_hostname ' ] )
if not hostsystem :
self . module . fail_json ( msg = " Failed to find a host named %(esxi_hostname)s " % self . params )
self . module . fail_json ( msg = ' Failed to find ESX host " %(esxi_hostname)s " ' % self . params )
return hostsystem
@ -991,11 +1019,10 @@ class PyVmomiHelper(object):
elif ' datastore ' in self . params [ ' disk ' ] [ 0 ] :
datastore_name = self . params [ ' disk ' ] [ 0 ] [ ' datastore ' ]
datastore = get _obj( self . content , [ vim . Datastore ] , datastore_name )
datastore = find _obj( self . content , [ vim . Datastore ] , datastore_name )
else :
self . module . fail_json ( msg = " Either datastore or autoselect_datastore "
" should be provided to select datastore " )
if not datastore and self . should_deploy_from_template ( ) :
self . module . fail_json ( msg = " Either datastore or autoselect_datastore should be provided to select datastore " )
if not datastore and self . params [ ' template ' ] :
# use the template's existing DS
disks = [ x for x in vm_obj . config . hardware . device if isinstance ( x , vim . vm . device . VirtualDisk ) ]
datastore = disks [ 0 ] . backing . datastore
@ -1017,7 +1044,13 @@ class PyVmomiHelper(object):
if current_parent is None :
return False
def select_resource_pool ( self , host ) :
def select_resource_pool_by_name ( self , resource_pool_name ) :
resource_pool = find_obj ( self . content , [ vim . ResourcePool ] , resource_pool_name )
if resource_pool is None :
self . module . fail_json ( msg = ' Could not find resource_pool " %s " ' % resource_pool_name )
return resource_pool
def select_resource_pool_by_host ( self , host ) :
resource_pools = get_all_objs ( self . content , [ vim . ResourcePool ] )
for rp in resource_pools . items ( ) :
if not rp [ 0 ] :
@ -1060,9 +1093,9 @@ class PyVmomiHelper(object):
# - multiple templates by the same name
# - static IPs
# datacenters = get_all_objs(self.content, [vim.Datacenter])
datacenter = get _obj( self . content , [ vim . Datacenter ] , self . params [ ' datacenter ' ] )
if not datacenter :
# datacenters = get_all_objs(self.content, [vim.Datacenter])
datacenter = find _obj( self . content , [ vim . Datacenter ] , self . params [ ' datacenter ' ] )
if datacenter is None :
self . module . fail_json ( msg = ' No datacenter named %(datacenter)s was found ' % self . params )
destfolder = None
@ -1074,19 +1107,22 @@ class PyVmomiHelper(object):
self . module . fail_json ( msg = ' No folder matched the path: %(folder)s ' % self . params )
destfolder = f_obj
hostsystem = self . select_host ( )
if self . should_deploy_from_template ( ) :
if self . params [ ' template ' ] :
# FIXME: need to search for this in the same way as guests to ensure accuracy
vm_obj = get _obj( self . content , [ vim . VirtualMachine ] , self . params [ ' template ' ] )
if not vm_obj :
vm_obj = find_obj ( self . content , [ vim . VirtualMachine ] , self . params [ ' template ' ] )
if vm_obj is None :
self . module . fail_json ( msg = " Could not find a template named %(template)s " % self . params )
else :
vm_obj = None
if self . params [ ' resource_pool ' ] :
resource_pool = self . select_resource_pool_by_name ( self . params [ ' resource_pool ' ] )
if resource_pool is None :
self . module . fail_json ( msg = ' Unable to find resource pool " %(resource_pool)s " ' % self . params )
# set the destination datastore for VM & disks
( datastore , datastore_name ) = self . select_datastore ( vm_obj )
resource_pool = self . select_resource_pool ( hostsystem )
self . configspec = vim . vm . ConfigSpec ( cpuHotAddEnabled = True , memoryHotAddEnabled = True )
self . configspec . deviceChange = [ ]
@ -1095,17 +1131,29 @@ class PyVmomiHelper(object):
self . configure_disks ( vm_obj = vm_obj )
self . configure_network ( vm_obj = vm_obj )
if len ( self . params [ ' customization ' ] ) > 0 or len ( self . params [ ' networks ' ] ) > 0 :
# Find if we need network customizations (find keys in dictionary that requires customizations)
network_changes = False
for nw in self . params [ ' networks ' ] :
for key in nw :
# We don't need customizations for these keys
if key not in ( ' device_type ' , ' mac ' , ' name ' , ' vlan ' ) :
network_changes = True
break
if len ( self . params [ ' customization ' ] ) > 0 or network_changes is True :
self . customize_vm ( vm_obj = vm_obj )
try :
if self . should_deploy_from_template ( ) :
if self . params[ ' template ' ] :
# create the relocation spec
relospec = vim . vm . RelocateSpec ( )
# Only provide specific host when using ESXi host directly
# Only select specific host when ESXi hostname is provided
if self . params [ ' esxi_hostname ' ] :
relospec . host = hostsystem
relospec . host = self . select_host ( )
relospec . datastore = datastore
if self . params [ ' resource_pool ' ] :
relospec . pool = resource_pool
if self . params [ ' snapshot_src ' ] is not None and self . params [ ' linked_clone ' ] :
@ -1116,11 +1164,9 @@ class PyVmomiHelper(object):
clonespec . customization = self . customspec
if self . params [ ' snapshot_src ' ] is not None :
snapshot = self . get_snapshots_by_name_recursively ( snapshots = vm_obj . snapshot . rootSnapshotList ,
snapname = self . params [ ' snapshot_src ' ] )
snapshot = self . get_snapshots_by_name_recursively ( snapshots = vm_obj . snapshot . rootSnapshotList , snapname = self . params [ ' snapshot_src ' ] )
if len ( snapshot ) != 1 :
self . module . fail_json ( msg = ' virtual machine " {0} " does not contain snapshot named " {1} " ' . format (
self . params [ ' template ' ] , self . params [ ' snapshot_src ' ] ) )
self . module . fail_json ( msg = ' virtual machine " %(template)s " does not contain snapshot named " %(snapshot_src)s " ' % self . params )
clonespec . snapshot = snapshot [ 0 ] . snapshot
@ -1189,12 +1235,16 @@ class PyVmomiHelper(object):
self . configspec . annotation = str ( self . params [ ' annotation ' ] )
self . change_detected = True
change_applied = False
relospec = vim . vm . RelocateSpec ( )
hostsystem = self . select_host ( )
relospec . pool = self . select_resource_pool ( hostsystem )
if self . params [ ' resource_pool ' ] :
relospec . pool = self . select_resource_pool _by_name( self . params [ ' resource_pool ' ] )
change_applied = False
if relospec . pool != self . current_vm_obj . resourcePool :
if relospec . pool is None :
self . module . fail_json ( msg = ' Unable to find resource pool " %(resource_pool)s " ' % self . params )
elif relospec . pool != self . current_vm_obj . resourcePool :
task = self . current_vm_obj . RelocateVM_Task ( spec = relospec )
self . wait_for_task ( task )
change_applied = True
@ -1232,7 +1282,7 @@ class PyVmomiHelper(object):
# https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.Task.html
# https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.TaskInfo.html
# https://github.com/virtdevninja/pyvmomi-community-samples/blob/master/samples/tools/tasks.py
while task . info . state not in [ ' success' , ' error ' ] :
while task . info . state not in [ ' error' , ' success ' ] :
time . sleep ( 1 )
def wait_for_vm_ip ( self , vm , poll = 100 , sleep = 5 ) :
@ -1251,61 +1301,20 @@ class PyVmomiHelper(object):
return facts
def get_obj ( content , vimtype , name ) :
"""
Return an object by name , if name is None the
first found object is returned
"""
obj = None
container = content . viewManager . CreateContainerView (
content . rootFolder , vimtype , True )
for c in container . view :
if name :
if c . name == name :
obj = c
break
else :
obj = c
break
container . Destroy ( )
return obj
def main ( ) :
module = AnsibleModule (
argument_spec = dict (
hostname = dict (
type = ' str ' ,
default = os . environ . get ( ' VMWARE_HOST ' )
) ,
username = dict (
type = ' str ' ,
default = os . environ . get ( ' VMWARE_USER ' )
) ,
password = dict (
type = ' str ' , no_log = True ,
default = os . environ . get ( ' VMWARE_PASSWORD ' )
) ,
state = dict (
required = False ,
choices = [
' poweredon ' ,
' poweredoff ' ,
' present ' ,
' absent ' ,
' restarted ' ,
' suspended ' ,
' shutdownguest ' ,
' rebootguest '
] ,
default = ' present ' ) ,
hostname = dict ( type = ' str ' , default = os . environ . get ( ' VMWARE_HOST ' ) ) ,
username = dict ( type = ' str ' , default = os . environ . get ( ' VMWARE_USER ' ) ) ,
password = dict ( type = ' str ' , default = os . environ . get ( ' VMWARE_PASSWORD ' ) , no_log = True ) ,
state = dict ( type = ' str ' , default = ' present ' ,
choices = [ ' absent ' , ' poweredoff ' , ' poweredon ' , ' present ' , ' rebootguest ' , ' restarted ' , ' shutdownguest ' , ' suspended ' ] ) ,
validate_certs = dict ( type = ' bool ' , default = True ) ,
template _src = dict ( type = ' str ' , aliases = [ ' template ' ] ) ,
template = dict ( type = ' str ' , aliases = [ ' template_src ' ] ) ,
is_template = dict ( type = ' bool ' , default = False ) ,
annotation = dict ( type = ' str ' , aliases = [ ' notes ' ] ) ,
customvalues = dict ( type = ' list ' , default = [ ] ) ,
name = dict ( required = True , type = ' str ' ) ,
name = dict ( type = ' str ' , required = True ) ,
name_match = dict ( type = ' str ' , default = ' first ' ) ,
uuid = dict ( type = ' str ' ) ,
folder = dict ( type = ' str ' , default = ' /vm ' ) ,
@ -1317,19 +1326,15 @@ def main():
esxi_hostname = dict ( type = ' str ' ) ,
cluster = dict ( type = ' str ' ) ,
wait_for_ip_address = dict ( type = ' bool ' , default = False ) ,
snapshot_src = dict ( type = ' str ' , default = None ),
snapshot_src = dict ( type = ' str ' ),
linked_clone = dict ( type = ' bool ' , default = False ) ,
networks = dict ( type = ' list ' , default = [ ] ) ,
resource_pool = dict ( type = ' str ' ) ,
customization = dict ( type = ' dict ' , no_log= True , default= { } ) ,
customization = dict ( type = ' dict ' , default= { } , no_log = True ) ,
) ,
supports_check_mode = True ,
mutually_exclusive = [
[ ' esxi_hostname ' , ' cluster ' ] ,
] ,
required_together = [
[ ' state ' , ' force ' ] ,
[ ' template ' ] ,
[ ' cluster ' , ' esxi_hostname ' ] ,
] ,
)
@ -1341,10 +1346,9 @@ def main():
module . params [ ' folder ' ] = module . params [ ' folder ' ] . rstrip ( ' / ' )
pyv = PyVmomiHelper ( module )
# Check if the VM exists before continuing
vm = pyv . getvm ( name = module . params [ ' name ' ] ,
folder = module . params [ ' folder ' ] ,
uuid = module . params [ ' uuid ' ] )
vm = pyv . getvm ( name = module . params [ ' name ' ] , folder = module . params [ ' folder ' ] , uuid = module . params [ ' uuid ' ] )
# VM already exists
if vm :
@ -1372,9 +1376,6 @@ def main():
# Create it ...
result = pyv . deploy_vm ( )
if ' failed ' not in result :
result [ ' failed ' ] = False
if result [ ' failed ' ] :
module . fail_json ( * * result )
else :