1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# Copyright: (c) 2019, Ansible Project 4# Copyright: (c) 2019, Pavan Bidkar <pbidkar@vmware.com> 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11DOCUMENTATION = r''' 12--- 13module: vmware_content_deploy_template 14short_description: Deploy Virtual Machine from template stored in content library. 15description: 16- Module to deploy virtual machine from template in content library. 17- Content Library feature is introduced in vSphere 6.0 version. 18- vmtx templates feature is introduced in vSphere 67U1 and APIs for clone template from content library in 67U2. 19- This module does not work with vSphere version older than 67U2. 20- All variables and VMware object names are case sensitive. 21author: 22- Pavan Bidkar (@pgbidkar) 23notes: 24- Tested on vSphere 6.7 U3 25requirements: 26- python >= 2.6 27- PyVmomi 28- vSphere Automation SDK 29options: 30 log_level: 31 description: 32 - The level of logging desired in this module. 33 type: str 34 required: False 35 default: 'normal' 36 choices: [ 'debug', 'info', 'normal' ] 37 version_added: '1.9.0' 38 template: 39 description: 40 - The name of template from which VM to be deployed. 41 type: str 42 required: True 43 aliases: ['template_src'] 44 library: 45 description: 46 - The name of the content library from where the template resides. 47 type: str 48 required: False 49 aliases: ['content_library', 'content_library_src'] 50 name: 51 description: 52 - The name of the VM to be deployed. 53 type: str 54 required: True 55 aliases: ['vm_name'] 56 datacenter: 57 description: 58 - Name of the datacenter, where VM to be deployed. 59 type: str 60 required: True 61 datastore: 62 description: 63 - Name of the datastore to store deployed VM and disk. 64 - Required if I(datastore_cluster) is not provided. 65 type: str 66 required: False 67 datastore_cluster: 68 description: 69 - Name of the datastore cluster to store deployed VM and disk. 70 - Please make sure Storage DRS is active for recommended datastore from the given datastore cluster. 71 - If Storage DRS is not enabled, datastore with largest free storage space is selected. 72 - Required if I(datastore) is not provided. 73 type: str 74 required: False 75 version_added: '1.7.0' 76 folder: 77 description: 78 - Name of the folder in datacenter in which to place deployed VM. 79 type: str 80 default: 'vm' 81 host: 82 description: 83 - Name of the ESX Host in datacenter in which to place deployed VM. 84 - The host has to be a member of the cluster that contains the resource pool. 85 - Required with I(resource_pool) to find resource pool details. This will be used as additional 86 information when there are resource pools with same name. 87 type: str 88 required: False 89 resource_pool: 90 description: 91 - Name of the resource pool in datacenter in which to place deployed VM. 92 - Required if I(cluster) is not specified. 93 - For default or non-unique resource pool names, specify I(host) and I(cluster). 94 - C(Resources) is the default name of resource pool. 95 type: str 96 required: False 97 cluster: 98 description: 99 - Name of the cluster in datacenter in which to place deployed VM. 100 - Required if I(resource_pool) is not specified. 101 type: str 102 required: False 103 state: 104 description: 105 - The state of Virtual Machine deployed from template in content library. 106 - If set to C(present) and VM does not exists, then VM is created. 107 - If set to C(present) and VM exists, no action is taken. 108 - If set to C(poweredon) and VM does not exists, then VM is created with powered on state. 109 - If set to C(poweredon) and VM exists, no action is taken. 110 type: str 111 required: False 112 default: 'present' 113 choices: [ 'present', 'poweredon' ] 114extends_documentation_fragment: 115- community.vmware.vmware_rest_client.documentation 116 117''' 118 119EXAMPLES = r''' 120- name: Deploy Virtual Machine from template in content library 121 community.vmware.vmware_content_deploy_template: 122 hostname: '{{ vcenter_hostname }}' 123 username: '{{ vcenter_username }}' 124 password: '{{ vcenter_password }}' 125 template: rhel_test_template 126 datastore: Shared_NFS_Volume 127 folder: vm 128 datacenter: Sample_DC_1 129 name: Sample_VM 130 resource_pool: test_rp 131 state: present 132 delegate_to: localhost 133 134- name: Deploy Virtual Machine from template in content library with PowerON State 135 community.vmware.vmware_content_deploy_template: 136 hostname: '{{ vcenter_hostname }}' 137 username: '{{ vcenter_username }}' 138 password: '{{ vcenter_password }}' 139 template: rhel_test_template 140 content_library: test_content_library 141 datastore: Shared_NFS_Volume 142 folder: vm 143 datacenter: Sample_DC_1 144 name: Sample_VM 145 resource_pool: test_rp 146 state: poweredon 147 delegate_to: localhost 148''' 149 150RETURN = r''' 151vm_deploy_info: 152 description: Virtual machine deployment message and vm_id 153 returned: on success 154 type: dict 155 sample: { 156 "msg": "Deployed Virtual Machine 'Sample_VM'.", 157 "vm_id": "vm-1009" 158 } 159''' 160 161from ansible.module_utils.basic import AnsibleModule 162from ansible_collections.community.vmware.plugins.module_utils.vmware_rest_client import VmwareRestClient 163from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi 164from ansible.module_utils._text import to_native 165 166HAS_VAUTOMATION_PYTHON_SDK = False 167try: 168 from com.vmware.vcenter.vm_template_client import LibraryItems 169 from com.vmware.vapi.std.errors_client import Error 170 HAS_VAUTOMATION_PYTHON_SDK = True 171except ImportError: 172 pass 173 174 175class VmwareContentDeployTemplate(VmwareRestClient): 176 def __init__(self, module): 177 """Constructor.""" 178 super(VmwareContentDeployTemplate, self).__init__(module) 179 180 # Initialize member variables 181 self.module = module 182 self._pyv = PyVmomi(module=module) 183 self._template_service = self.api_client.vcenter.vm_template.LibraryItems 184 self._datacenter_id = None 185 self._datastore_id = None 186 self._library_item_id = None 187 self._folder_id = None 188 self._host_id = None 189 self._cluster_id = None 190 self._resourcepool_id = None 191 self.result = {} 192 193 # Turn on debug if not specified, but ANSIBLE_DEBUG is set 194 if self.module._debug: 195 self.warn('Enable debug output because ANSIBLE_DEBUG was set.') 196 self.params['log_level'] = 'debug' 197 self.log_level = self.params['log_level'] 198 if self.log_level == 'debug': 199 # Turn on debugging 200 self.result['debug'] = {} 201 202 # Get parameters 203 self.template = self.params.get('template') 204 self.library = self.params.get('library') 205 self.vm_name = self.params.get('name') 206 self.datacenter = self.params.get('datacenter') 207 self.datastore = self.params.get('datastore') 208 self.datastore_cluster = self.params.get('datastore_cluster') 209 self.folder = self.params.get('folder') 210 self.resourcepool = self.params.get('resource_pool') 211 self.cluster = self.params.get('cluster') 212 self.host = self.params.get('host') 213 214 vm = self._pyv.get_vm() 215 if vm: 216 self.result['vm_deploy_info'] = dict( 217 msg="Virtual Machine '%s' already Exists." % self.vm_name, 218 vm_id=vm._moId, 219 ) 220 self._fail(msg="Virtual Machine deployment failed") 221 222 def deploy_vm_from_template(self, power_on=False): 223 # Find the datacenter by the given datacenter name 224 self._datacenter_id = self.get_datacenter_by_name(self.datacenter) 225 if not self._datacenter_id: 226 self._fail(msg="Failed to find the datacenter %s" % self.datacenter) 227 228 # Find the datastore by the given datastore name 229 if self.datastore: 230 self._datastore_id = self.get_datastore_by_name(self.datacenter, self.datastore) 231 if not self._datastore_id: 232 self._fail(msg="Failed to find the datastore %s" % self.datastore) 233 234 # Find the datastore by the given datastore cluster name 235 if self.datastore_cluster and not self._datastore_id: 236 dsc = self._pyv.find_datastore_cluster_by_name(self.datastore_cluster) 237 if dsc: 238 self.datastore = self._pyv.get_recommended_datastore(dsc) 239 self._datastore_id = self.get_datastore_by_name(self.datacenter, self.datastore) 240 else: 241 self._fail(msg="Failed to find the datastore cluster %s" % self.datastore_cluster) 242 243 if not self._datastore_id: 244 self._fail(msg="Failed to find the datastore using either datastore or datastore cluster") 245 246 # Find the LibraryItem (Template) by the given LibraryItem name 247 if self.library: 248 self._library_item_id = self.get_library_item_from_content_library_name( 249 self.template, self.library 250 ) 251 if not self._library_item_id: 252 self._fail(msg="Failed to find the library Item %s in content library %s" % (self.template, self.library)) 253 else: 254 self._library_item_id = self.get_library_item_by_name(self.template) 255 if not self._library_item_id: 256 self._fail(msg="Failed to find the library Item %s" % self.template) 257 258 # Find the folder by the given FQPN folder name 259 # The FQPN is I(datacenter)/I(folder type)/folder name/... for 260 # example Lab/vm/someparent/myfolder is a vm folder in the Lab datacenter. 261 folder_obj = self._pyv.find_folder_by_fqpn(self.folder, self.datacenter, folder_type='vm') 262 if folder_obj: 263 self._folder_id = folder_obj._moId 264 if not self._folder_id: 265 self._fail(msg="Failed to find the folder %s" % self.folder) 266 267 # Find the Host by the given name 268 if self.host: 269 self._host_id = self.get_host_by_name(self.datacenter, self.host) 270 if not self._host_id: 271 self._fail(msg="Failed to find the Host %s" % self.host) 272 273 # Find the Cluster by the given Cluster name 274 if self.cluster: 275 self._cluster_id = self.get_cluster_by_name(self.datacenter, self.cluster) 276 if not self._cluster_id: 277 self._fail(msg="Failed to find the Cluster %s" % self.cluster) 278 cluster_obj = self.api_client.vcenter.Cluster.get(self._cluster_id) 279 self._resourcepool_id = cluster_obj.resource_pool 280 281 # Find the resourcepool by the given resourcepool name 282 if self.resourcepool and self.cluster and self.host: 283 self._resourcepool_id = self.get_resource_pool_by_name(self.datacenter, self.resourcepool, self.cluster, self.host) 284 if not self._resourcepool_id: 285 self._fail(msg="Failed to find the resource_pool %s" % self.resourcepool) 286 287 # Create VM placement specs 288 self.placement_spec = LibraryItems.DeployPlacementSpec(folder=self._folder_id) 289 if self._host_id: 290 self.placement_spec.host = self._host_id 291 if self._resourcepool_id: 292 self.placement_spec.resource_pool = self._resourcepool_id 293 if self._cluster_id: 294 self.placement_spec.cluster = self._cluster_id 295 self.vm_home_storage_spec = LibraryItems.DeploySpecVmHomeStorage( 296 datastore=to_native(self._datastore_id) 297 ) 298 self.disk_storage_spec = LibraryItems.DeploySpecDiskStorage( 299 datastore=to_native(self._datastore_id) 300 ) 301 self.deploy_spec = LibraryItems.DeploySpec( 302 name=self.vm_name, 303 placement=self.placement_spec, 304 vm_home_storage=self.vm_home_storage_spec, 305 disk_storage=self.disk_storage_spec, 306 powered_on=power_on 307 ) 308 vm_id = '' 309 try: 310 vm_id = self._template_service.deploy(self._library_item_id, self.deploy_spec) 311 except Error as error: 312 self._fail(msg="%s" % self.get_error_message(error)) 313 except Exception as err: 314 self._fail(msg="%s" % to_native(err)) 315 316 if not vm_id: 317 self.result['vm_deploy_info'] = dict( 318 msg="Virtual Machine deployment failed", 319 vm_id='' 320 ) 321 self._fail(msg="Virtual Machine deployment failed") 322 self.result['changed'] = True 323 self.result['vm_deploy_info'] = dict( 324 msg="Deployed Virtual Machine '%s'." % self.vm_name, 325 vm_id=vm_id, 326 ) 327 self._exit() 328 329 # 330 # Wrap AnsibleModule methods 331 # 332 333 def _mod_debug(self): 334 if self.log_level == 'debug': 335 self.result['debug'] = dict( 336 datacenter_id=self._datacenter_id, 337 datastore_id=self._datastore_id, 338 library_item_id=self._library_item_id, 339 folder_id=self._folder_id, 340 host_id=self._host_id, 341 cluster_id=self._cluster_id, 342 resourcepool_id=self._resourcepool_id 343 ) 344 345 def _fail(self, msg): 346 self._mod_debug() 347 self.module.fail_json(msg=msg, **self.result) 348 349 def _exit(self): 350 self._mod_debug() 351 self.module.exit_json(**self.result) 352 353 354def main(): 355 argument_spec = VmwareRestClient.vmware_client_argument_spec() 356 argument_spec.update( 357 log_level=dict( 358 type='str', 359 choices=[ 360 'debug', 361 'info', 362 'normal', 363 ], 364 default='normal' 365 ), 366 state=dict( 367 type='str', 368 choices=[ 369 'present', 370 'poweredon' 371 ], 372 default='present' 373 ), 374 template=dict( 375 type='str', 376 aliases=[ 377 'template_src' 378 ], 379 required=True 380 ), 381 library=dict( 382 type='str', 383 aliases=[ 384 'content_library', 385 'content_library_src', 386 ], 387 required=False 388 ), 389 name=dict( 390 type='str', 391 aliases=[ 392 'vm_name' 393 ], 394 required=True, 395 ), 396 datacenter=dict( 397 type='str', 398 required=True 399 ), 400 datastore=dict( 401 type='str', 402 required=False 403 ), 404 datastore_cluster=dict( 405 type='str', 406 required=False 407 ), 408 folder=dict( 409 type='str', 410 default='vm' 411 ), 412 host=dict( 413 type='str', 414 required=False 415 ), 416 resource_pool=dict( 417 type='str', 418 required=False 419 ), 420 cluster=dict( 421 type='str', 422 required=False 423 ), 424 ) 425 module = AnsibleModule( 426 argument_spec=argument_spec, 427 supports_check_mode=True, 428 required_one_of=[ 429 ['datastore', 'datastore_cluster'], 430 ['host', 'cluster'], 431 ], 432 ) 433 434 result = {'failed': False, 'changed': False} 435 vmware_contentlib_create = VmwareContentDeployTemplate(module) 436 if module.params['state'] == 'present': 437 if module.check_mode: 438 result.update( 439 vm_name=module.params['name'], 440 changed=True, 441 desired_operation='Create VM with PowerOff State', 442 ) 443 module.exit_json(**result) 444 vmware_contentlib_create.deploy_vm_from_template() 445 elif module.params['state'] == 'poweredon': 446 if module.check_mode: 447 result.update( 448 vm_name=module.params['name'], 449 changed=True, 450 desired_operation='Create VM with PowerON State', 451 ) 452 module.exit_json(**result) 453 vmware_contentlib_create.deploy_vm_from_template(power_on=True) 454 else: 455 result.update( 456 vm_name=module.params['name'], 457 changed=False, 458 desired_operation="State '%s' is not implemented" % module.params['state'] 459 ) 460 module.fail_json(**result) 461 462 463if __name__ == '__main__': 464 main() 465