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