From 657e958e8f63e4c96f39f4c229eca4304e7f66b7 Mon Sep 17 00:00:00 2001 From: Mike Morency Date: Wed, 10 Jul 2024 13:08:13 -0400 Subject: [PATCH] add folder_template_from_vm module --- .../fragments/10-rename_get_vm_by_name.yaml | 3 + .../fragments/48-add_folder_template.yml | 2 + plugins/doc_fragments/vmware.py | 20 ++ plugins/module_utils/vmware.py | 145 +++++++- plugins/module_utils/vmware_rest_client.py | 2 +- plugins/modules/content_template.py | 2 +- plugins/modules/folder_template_from_vm.py | 328 ++++++++++++++++++ .../defaults/main.yml | 17 + .../vmware_folder_template_from_vm/run.yml | 14 + .../vmware_folder_template_from_vm/runme.sh | 3 + .../tasks/main.yml | 87 +++++ .../vmware_folder_template_from_vm/vars.yml | 9 + 12 files changed, 629 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/10-rename_get_vm_by_name.yaml create mode 100644 changelogs/fragments/48-add_folder_template.yml create mode 100644 plugins/modules/folder_template_from_vm.py create mode 100644 tests/integration/targets/vmware_folder_template_from_vm/defaults/main.yml create mode 100644 tests/integration/targets/vmware_folder_template_from_vm/run.yml create mode 100755 tests/integration/targets/vmware_folder_template_from_vm/runme.sh create mode 100644 tests/integration/targets/vmware_folder_template_from_vm/tasks/main.yml create mode 100644 tests/integration/targets/vmware_folder_template_from_vm/vars.yml diff --git a/changelogs/fragments/10-rename_get_vm_by_name.yaml b/changelogs/fragments/10-rename_get_vm_by_name.yaml new file mode 100644 index 00000000..656e9f9c --- /dev/null +++ b/changelogs/fragments/10-rename_get_vm_by_name.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - module_utils/vmware_rest_client - rename get_vm_by_name method as there is same signature already diff --git a/changelogs/fragments/48-add_folder_template.yml b/changelogs/fragments/48-add_folder_template.yml new file mode 100644 index 00000000..996abb4c --- /dev/null +++ b/changelogs/fragments/48-add_folder_template.yml @@ -0,0 +1,2 @@ +minor_changes: + - folder_template_from_vm - add module and tests to create a template from an existing VM in vcenter and store the template in a folder diff --git a/plugins/doc_fragments/vmware.py b/plugins/doc_fragments/vmware.py index 18fe0197..7eaa942b 100644 --- a/plugins/doc_fragments/vmware.py +++ b/plugins/doc_fragments/vmware.py @@ -46,6 +46,16 @@ class ModuleDocFragment(object): - If the value is not specified in the task, the value of environment variable E(VMWARE_PORT) will be used instead. type: int default: 443 + datacenter: + description: + - The datacenter to use when connecting to a vCenter. + type: str + aliases: [ datacenter_name ] + cluster: + description: + - The cluster to use when connecting to a vCenter. + type: str + aliases: [ cluster_name ] proxy_host: description: - Address of a proxy that will receive all HTTPS requests and relay them. @@ -95,6 +105,16 @@ class ModuleDocFragment(object): - If the value is not specified in the task, the value of environment variable E(VMWARE_PORT) will be used instead. type: int default: 443 + datacenter: + description: + - The datacenter to use when connecting to a vCenter. + type: str + aliases: [ datacenter_name ] + cluster: + description: + - The cluster to use when connecting to a vCenter. + type: str + aliases: [ cluster_name ] proxy_host: description: - Address of a proxy that will receive all HTTPS requests and relay them. diff --git a/plugins/module_utils/vmware.py b/plugins/module_utils/vmware.py index 1e3d1c04..514fa89d 100644 --- a/plugins/module_utils/vmware.py +++ b/plugins/module_utils/vmware.py @@ -29,7 +29,7 @@ PYVMOMI_IMP_ERR = None try: from pyVim import connect - from pyVmomi import vim, vmodl + from pyVmomi import vim, vmodl, VmomiSupport HAS_PYVMOMI = True except ImportError: PYVMOMI_IMP_ERR = traceback.format_exc() @@ -58,6 +58,12 @@ def vmware_argument_spec(): required=False, no_log=True, fallback=(env_fallback, ['VMWARE_PASSWORD'])), + cluster=dict(type='str', + aliases=['cluster_name'], + required=False), + datacenter=dict(type='str', + aliases=['datacenter_name'], + required=False), port=dict(type='int', default=443, fallback=(env_fallback, ['VMWARE_PORT'])), @@ -218,3 +224,140 @@ def is_vcenter(self): return True elif api_type == 'HostAgent': return False + + def __get_objs_by_name(self, vimtype, name, first_only=False): + """ + Get any vsphere object associated with a given text name and vim type. + Args: + vimtype: The type of object to search for + name: The name of the object to search for + first_only: If true, return once the first object is found. + Useful when names must be unique + Returns: + list(object) or list() if no matches are found + """ + objs = [] + container = self.content.viewManager.CreateContainerView(self.content.rootFolder, vimtype, True) + for c in container.view: + if c.name == name: + if first_only: + return c + objs += [c] + + return objs if objs else None + + def get_vm_by_name(self, vm_name, fail_on_missing=False, name_match=None): + """ + Get the vms matching the given name. VM names may not be unique + in a given cluster. + Args: + vm_name: Name of the VM to search for + fail_on_missing: If true, an error will be thrown if no VMs are found + name_match: If provided, return one VM from the list of VMs. Either 'first' or 'last' + Returns: + list(vm), vm if name_match is provided, or None if no matches were found + """ + vms = self.__get_objs_by_name([vim.VirtualMachine], vm_name) + if not vms and fail_on_missing: + self.module.fail_json("Unable to find VM with name %s" % vm_name) + if vms and name_match: + if name_match == 'first': + return vms[0] + elif name_match == 'last': + return vms[-1] + else: + self.module.fail_json("Unrecognized name_match option '%s' in get_vm_by_name method" % name_match) + + return vms + + def get_folder_by_name(self, folder_name, fail_on_missing=False): + """ + Get all folders with the given name. Names are not unique + in a given cluster, so multiple folder objects can be returned + Args: + folder_name: Name of the folder to search for + fail_on_missing: If true, an error will be thrown if no folders are found + Returns: + list(folder object) or None + """ + folder = self.__get_objs_by_name([vim.Folder], folder_name) + if not folder and fail_on_missing: + self.module.fail_json("Unable to find folder with name %s" % folder_name) + return folder + + def get_folder_by_absolute_path(self, folder_path, fail_on_missing=False): + """ + Get a folders with the given absolute path. Paths are unique so only + one folder can be returned at most. An absolute path might look like + 'Datacenter Name/vm/my/folder/structure' + Args: + folder_path: The absolute path to a folder to search for + fail_on_missing: If true, an error will be thrown if no folders are found + Returns: + folder object or None + """ + folder = self.si.content.searchIndex.FindByInventoryPath(folder_path) + if not folder and fail_on_missing: + self.module.fail_json("Unable to find folder with absolute path %s" % folder_path) + return folder + + def get_datastore_by_name(self, ds_name, fail_on_missing=False): + """ + Get the datastore matching the given name. Datastore names must be unique + in a given cluster, so only one object is returned at most. + Args: + ds_name: Name of the datastore to search for + fail_on_missing: If true, an error will be thrown if no datastores are found + Returns: + datastore object or None + """ + ds = self.__get_objs_by_name([vim.Datastore], ds_name, first_only=True) + if not ds and fail_on_missing: + self.module.fail_json("Unable to find datastore with name %s" % ds_name) + return ds + + def get_resource_pool_by_name(self, pool_name, fail_on_missing=False): + """ + Get the resource pool matching the given name. Pool names must be unique + in a given cluster, so only one object is returned at most. + Args: + pool_name: Name of the pool to search for + fail_on_missing: If true, an error will be thrown if no pools are found + Returns: + resource pool object or None + """ + pool = self.__get_objs_by_name([vim.ResourcePool], pool_name, first_only=True) + if not pool and fail_on_missing: + self.module.fail_json("Unable to find resource pool with name %s" % pool_name) + return pool + + def get_vm_by_uuid(self, vm_uuid, use_instance_uuid=True, fail_on_missing=False): + """ + Search for a VM using the instance UUID or BIOS UUID. BIOS UUID is considered an older + approach and is not garunteed to be unique. + Args: + vm_uuid: The uuid of the instance to search for + use_instance_uuid: If false, search for the BIOS UUID instead of the instance UUID + fail_on_missing: If true, an error will be thrown if no VMs are found + """ + vm = self.si.content.searchIndex.FindByUuid( + instanceUuid=use_instance_uuid, + uuid=vm_uuid, + vmSearch=True + ) + if not vm and fail_on_missing: + self.module.fail_json("Unable to find VM with UUID %s" % vm_uuid) + return vm + + def get_vm_by_moid(self, vm_moid, fail_on_missing=False): + """ + Search for a VM using the instance MOID or moREF. MOID is common in the VMWare REST + API but still usable in the SOAP API. + Args: + vm_moid: The uuid of the instance to search for + fail_on_missing: If true, an error will be thrown if no VMs are found + """ + vm = VmomiSupport.templateOf('VirtualMachine')(vm_moid, self.si._stub) + if not vm and fail_on_missing: + self.module.fail_json("Unable to find VM with MOID %s" % vm_moid) + return vm diff --git a/plugins/module_utils/vmware_rest_client.py b/plugins/module_utils/vmware_rest_client.py index 65c28e28..bfdb9c04 100644 --- a/plugins/module_utils/vmware_rest_client.py +++ b/plugins/module_utils/vmware_rest_client.py @@ -312,7 +312,7 @@ def get_host_by_name(self, host_name, datacenter_name=None): host_summaries = self.api_client.vcenter.Host.list(filter_spec) return host_summaries[0].host if len(host_summaries) > 0 else None - def get_vm_by_name(self, vm_name, datacenter_name=None): + def get_vm_obj_by_name(self, vm_name, datacenter_name=None): """ Returns the identifier of a VM with the mentioned names. """ diff --git a/plugins/modules/content_template.py b/plugins/modules/content_template.py index dd6fad5f..f88f2acf 100644 --- a/plugins/modules/content_template.py +++ b/plugins/modules/content_template.py @@ -157,7 +157,7 @@ def create_template_from_vm(self): name=self.template, placement=placement_spec, library=self.get_library_by_name(self.library), - source_vm=self.get_vm_by_name(self.vm_name), + source_vm=self.get_vm_obj_by_name(self.vm_name), ) template_id = '' try: diff --git a/plugins/modules/folder_template_from_vm.py b/plugins/modules/folder_template_from_vm.py new file mode 100644 index 00000000..ffd64bac --- /dev/null +++ b/plugins/modules/folder_template_from_vm.py @@ -0,0 +1,328 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Ansible Cloud Team (@ansible-collections) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: folder_template_from_vm +short_description: Create a template in a local VCenter folder from an existing VM +description: + - >- + This module creates a template in a local VCenter folder from an existing VM. The folder must already exist. + The VM must be powered off, and is otherwise unchanged. If the template already exists and the desired state + is 'present', nothing is done. +author: + - Ansible Cloud Team (@ansible-collections) +requirements: + - pyvmomi +options: + vm_name: + description: + - The name of the vm to be used to create the template + - One of vm_name, vm_moid, vm_uuid is required + - This parameter is ignored when state is 'absent' + type: str + required: False + vm_uuid: + description: + - The UUID of the vm to be used to create the template + - One of vm_name, vm_moid, vm_uuid is required + - This parameter is ignored when state is 'absent' + type: str + required: False + vm_moid: + description: + - The MOID of the vm to be used to create the template + - One of vm_name, vm_moid, vm_uuid is required + - This parameter is ignored when state is 'absent' + type: str + required: False + vm_use_instance_uuid: + description: + - If true, search by instance UUID instead of BIOS UUID. + - BIOS UUID may not be unique and may cause errors. + type: bool + required: False + default: True + vm_name_match: + description: + - If using vm_name and multiple VMs have the same name, specify which VM should be selected + type: str + required: False + choices: ['first', 'last'] + template_folder: + description: + - The name of the folder that the new template should be placed in + - Should be the full folder path, with or without the 'datacenter/vm/' prefix + - For example 'datacenter name/vm/path/to/folder' or 'path/to/folder' + type: str + required: True + template_name: + description: + - The name to give to the new template. + type: str + required: True + state: + description: + - If the template should be present or absent + type: str + required: False + default: present + choices: ['present', 'absent'] + datacenter: + description: + - The name of datacenter in which to operate + type: str + aliases: ['datacenter_name'] + required: True + datastore: + description: + - The name of datastore to use as storage for the template. + type: str + resource_pool: + description: + - The resource pool to place the template in. + type: str + wait_for_template: + description: + - If true, the module will wait until the template is created to exit. + type: bool + default: True +attributes: + check_mode: + description: The check_mode support. + support: full +extends_documentation_fragment: + - vmware.vmware.vmware.vcenter_documentation + +''' + +EXAMPLES = r''' +- name: Create A New Template Using VM UUID + vmware.vmware.folder_template: + hostname: "https://vcenter" + username: "username" + password: "password" + datacenter: "my-datacenter" + vm_uuid: "11111111-11111111-11111111" + template_folder: "my-datacenter/vm/netsted/folder/path/tempaltes" + template_name: "my_template" + +- name: Create A New Template Using VM Name + vmware.vmware.folder_template_from_vm: + hostname: "https://vcenter" + username: "username" + password: "password" + datacenter: "my-datacenter" + vm_name: "my_vm" + vm_name_match: "first" + template_name: "my_template" + template_folder: "nested/folder/path/templates" + +- name: Destroy A Template In A Folder + vmware.vmware.folder_template_from_vm: + hostname: "https://vcenter" + username: "username" + password: "password" + datacenter: "my-datacenter" + vm_name: "foo" + state: "absent" + template_name: "my_template" + template_folder: "nested/folder/path/templates" +''' + +RETURN = r''' +''' + +import time +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vmware.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec + +PYVMOMI_IMP_ERR = None +try: + from pyVmomi import vim + HAS_PYVMOMI = True +except ImportError: + PYVMOMI_IMP_ERR = traceback.format_exc() + HAS_PYVMOMI = False + + +class VmwareFolderTemplate(PyVmomi): + def __init__(self, module): + super(VmwareFolderTemplate, self).__init__(module) + if not self.is_vcenter(): + self.module.fail_json("Only VCenter clusters are supported for this module.") + + self.vm_name = self.params.get("vm_name") + self.vm_uuid = self.params.get("vm_uuid") + self.vm_use_instance_uuid = self.params.get("vm_use_instance_uuid") + self.vm_moid = self.params.get("vm_moid") + + self.template_name = self.params.get("template_name") + + self.template_folder = self.__init_template_folder_param() + self.vm_name_match = self.params.get("vm_name_match") + self.datastore = self.params.get("datastore") + self.resource_pool = self.params.get("resource_pool") + self.wait_for_template = self.params.get("wait_for_template") + + def __init_template_folder_param(self): + """ + Formats an absolute folder path for use within the class. If path does not start with + the datacenter name, we add that and 'vm' to the start of the path. + Eg: rest/of/path -> datacenter name/vm/rest/of/path + """ + folder_name = self.params.get("template_folder") + datacenter_name = self.params.get("datacenter") + if not folder_name.startswith((datacenter_name, '/' + datacenter_name)): + folder_name = datacenter_name + '/vm/' + folder_name + + return self.get_folder_by_absolute_path(folder_name, fail_on_missing=True) + + def check_if_template_exists(self): + """ + Checks if a template with the given name and folder already exists + """ + templates = self.get_vm_by_name(self.template_name, fail_on_missing=False) + if not templates: + return False + + for template in templates: + if not template.parent == self.template_folder: + continue + + if template.config.template: + return template + else: + self.module.fail_json("A virtual machine already exists with desired template name, %s." % self.template_name) + + return False + + def __get_source_vm(self): + """ + Uses the UUID, MOID, or name provided to find the source VM for the template. Returns an error if using the name, + multiple matches are found, and the user did not provide a name_match strategy. + """ + if self.vm_uuid: + vm = self.get_vm_by_uuid(self.vm_uuid, use_instance_uuid=self.vm_use_instance_uuid, fail_on_missing=True) + + elif self.vm_moid: + vm = self.get_vm_by_moid(self.vm_moid, fail_on_missing=True) + + elif self.vm_name: + vm = self.get_vm_by_name(self.vm_name, fail_on_missing=True, name_match=self.vm_name_match) + if isinstance(vm, list): + if len(vm) == 1: + vm = vm[0] + else: + self.module.fail_json("Multiple VMs found with name %s. Try using the vm_name_match or vm_uuid/vm_moid attributes." % self.vm_name) + + if vm.runtime.powerState != 'poweredOff': + self.module.fail_json(msg="VM must be in powered off state before creating a template from it.") + + return vm + + def create_template_in_folder(self): + """ + Clones an existing VM into a new template instance. + """ + vm = self.__get_source_vm() + template_location_spec = self.__create_template_location_spec() + template_spec = vim.vm.CloneSpec(powerOn=False, template=True, location=template_location_spec) + if self.module.check_mode: + return + + task = vm.Clone( + name=self.template_name, + folder=self.template_folder, + spec=template_spec + ) + + if self.wait_for_template: + self.__wait_for_template(task) + + def __create_template_location_spec(self): + template_location_spec = vim.vm.RelocateSpec() + if self.datastore: + template_location_spec.datastore = self.get_datastore_by_name(self.datastore, fail_on_missing=True) + if self.resource_pool: + template_location_spec.pool = self.get_resource_pool_by_name(self.resource_pool, fail_on_missing=True) + + return template_location_spec + + def __wait_for_template(self, task): + """ + Waits and provides updates on a vSphere task + """ + while task.info.state == vim.TaskInfo.State.running: + time.sleep(2) + + if task.info.state == vim.TaskInfo.State.success: + return + else: + self.module.fail_json(msg=task.info.error) + + +def custom_validation(module): + """ + This validation is too complex to be done with the provided ansible validation + """ + if module.params.get('state') == 'present': + if (not module.params.get('vm_name') and + not module.params.get('vm_uuid') and + not module.params.get('vm_moid')): + module.fail_json("One of vm_name, vm_uuid, or vm_moid is required when state is 'present'") + + +def main(): + module = AnsibleModule( + argument_spec={ + **vmware_argument_spec(), **dict( + vm_name=dict(type='str', required=False, default=None), + vm_name_match=dict(type='str', required=False, choices=['first', 'last']), + vm_uuid=dict(type='str', required=False, default=None), + vm_use_instance_uuid=dict(type='bool', required=False, default=True), + vm_moid=dict(type='str', required=False, default=None), + state=dict(type='str', required=False, default='present', choices=['present', 'absent']), + template_name=dict(type='str', required=True), + template_folder=dict(type='str', required=True), + datacenter=dict(type='str', aliases=['datacenter_name'], required=True), + datastore=dict(type='str', required=False), + resource_pool=dict(type='str', required=False), + wait_for_template=dict(type='bool', required=False, default=True), + ) + }, + mutually_exclusive=[['vm_name', 'vm_uuid', 'vm_moid']], + supports_check_mode=True, + ) + + result = dict( + changed=False, + ) + + custom_validation(module) + folder_template = VmwareFolderTemplate(module) + if module.params.get('state') == 'present': + if not folder_template.check_if_template_exists(): + folder_template.create_template_in_folder() + result['changed'] = True + else: + template = folder_template.check_if_template_exists() + if template: + if not module.check_mode: + template.Destroy_Task() + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/vmware_folder_template_from_vm/defaults/main.yml b/tests/integration/targets/vmware_folder_template_from_vm/defaults/main.yml new file mode 100644 index 00000000..795bf754 --- /dev/null +++ b/tests/integration/targets/vmware_folder_template_from_vm/defaults/main.yml @@ -0,0 +1,17 @@ +vcenter_port: 443 +run_on_simulator: false + +template_folder: e2e-qe + +vm: test-folder_template +vm_cluster: "Eco-Cluster" +vm_datacenter: "Eco-Datacenter" +vm_guest_id: "rhel8_64Guest" +vm_disk: + - size_gb: 10 + type: thin + autoselect_datastore: true +vm_hardware: + memory_mb: 2000 + num_cpus: 2 + boot_firmware: efi diff --git a/tests/integration/targets/vmware_folder_template_from_vm/run.yml b/tests/integration/targets/vmware_folder_template_from_vm/run.yml new file mode 100644 index 00000000..4510ac6f --- /dev/null +++ b/tests/integration/targets/vmware_folder_template_from_vm/run.yml @@ -0,0 +1,14 @@ +- hosts: localhost + gather_facts: no + collections: + - community.general + vars_files: + - vars.yml + tasks: + - name: Vcsim + ansible.builtin.import_role: + name: prepare_vcsim + + - name: Import vmware_folder_template role + ansible.builtin.import_role: + name: vmware_folder_template_from_vm diff --git a/tests/integration/targets/vmware_folder_template_from_vm/runme.sh b/tests/integration/targets/vmware_folder_template_from_vm/runme.sh new file mode 100755 index 00000000..a4c36631 --- /dev/null +++ b/tests/integration/targets/vmware_folder_template_from_vm/runme.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +source ../init.sh +exec ansible-playbook run.yml diff --git a/tests/integration/targets/vmware_folder_template_from_vm/tasks/main.yml b/tests/integration/targets/vmware_folder_template_from_vm/tasks/main.yml new file mode 100644 index 00000000..86266596 --- /dev/null +++ b/tests/integration/targets/vmware_folder_template_from_vm/tasks/main.yml @@ -0,0 +1,87 @@ +--- +- block: + - name: "Test setup: Create Folder" + community.vmware.vcenter_folder: + validate_certs: false + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + port: "{{ vcenter_port }}" + datacenter: "{{ vm_datacenter }}" + folder_name: "{{ template_folder }}" + folder_type: vm + state: present + when: run_on_simulator + + - name: "Test setup: Create VM guest {{ vm }}" + community.vmware.vmware_guest: + validate_certs: false + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + cluster: "{{ vm_cluster }}" + port: "{{ vcenter_port }}" + datacenter: "{{ vm_datacenter }}" + folder: "{{ template_folder }}" + state: present + name: "{{ vm }}" + disk: "{{ vm_disk }}" + guest_id: "{{ vm_guest_id }}" + hardware: "{{ vm_hardware }}" + when: not run_on_simulator + + - name: "Test setup: Power Off VM guest {{ vm }}" + community.vmware.vmware_guest: + validate_certs: false + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + cluster: "{{ vm_cluster }}" + datacenter: "{{ vm_datacenter }}" + port: "{{ vcenter_port }}" + state: poweredoff + name: "{{ vm }}" + when: run_on_simulator + + - name: Create template from vm in vcenter folder + vmware.vmware.folder_template_from_vm: + validate_certs: false + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + datacenter: "{{ vm_datacenter }}" + port: "{{ vcenter_port }}" + template_folder: "{{ template_folder }}" + vm_name: "{{ vm }}" + template_name: "test_template" + register: __res + + always: + - name: "Test teardown: Destroy VM guest {{ vm }}" + community.vmware.vmware_guest: + validate_certs: false + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + cluster: "{{ vm_cluster }}" + port: "{{ vcenter_port }}" + datacenter: "{{ vm_datacenter }}" + folder: "{{ vm_folder }}" + state: absent + force: true + name: "{{ vm }}" + when: not run_on_simulator + - name: "Test teardown: Destroy VM template {{ vm }}" + community.vmware.vmware_guest: + validate_certs: false + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + cluster: "{{ vm_cluster }}" + port: "{{ vcenter_port }}" + datacenter: "{{ vm_datacenter }}" + folder: "{{ vm_folder }}" + state: absent + force: true + name: "{{ vm }}_template" + when: not run_on_simulator diff --git a/tests/integration/targets/vmware_folder_template_from_vm/vars.yml b/tests/integration/targets/vmware_folder_template_from_vm/vars.yml new file mode 100644 index 00000000..493ef902 --- /dev/null +++ b/tests/integration/targets/vmware_folder_template_from_vm/vars.yml @@ -0,0 +1,9 @@ +vcenter_hostname: "127.0.0.1" +vcenter_username: "user" +vcenter_password: "pass" +vcenter_port: 8989 +vm_cluster: DC0_C0 +vm_datacenter: DC0 +vm_folder: test +run_on_simulator: true +vm: DC0_H0_VM0