Skip to content

Commit

Permalink
create template from vm in folder
Browse files Browse the repository at this point in the history
  • Loading branch information
mikemorency committed Jul 12, 2024
1 parent f867468 commit 969915d
Show file tree
Hide file tree
Showing 9 changed files with 714 additions and 1 deletion.
2 changes: 2 additions & 0 deletions changelogs/fragments/48-add_folder_template.yml
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions plugins/doc_fragments/vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
146 changes: 145 additions & 1 deletion plugins/module_utils/vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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'])),
Expand Down Expand Up @@ -218,3 +224,141 @@ 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 folder with the given path. Paths are unique when they are absolute 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
69 changes: 69 additions & 0 deletions plugins/module_utils/vmware_folder_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- 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


FOLDER_TYPES = ('vm', 'host', 'network', 'datastore')


def __prepend_datacenter_and_folder_type(folder_path, datacenter_name, folder_type=None):
"""
Formats a folder path so it is absolute, meaning it includes the datacenter name and
type (vm, host, etc) at the start of the path. If path already starts with
the datacenter name, nothing is added.
Eg: rest/of/path -> datacenter name/type/rest/of/path
"""
folder_path = folder_path.lstrip('/')
if folder_path.startswith(datacenter_name):
return folder_path

if folder_type not in FOLDER_TYPES:
raise ValueError("folder_type %s not in acceptable " % folder_type +
"folder type values %s" % ', '.join(FOLDER_TYPES))

return '/'.join([datacenter_name, folder_type, folder_path])


def format_folder_path_as_vm_fq_path(folder_path, datacenter_name):
"""
Formats a VM folder path so it is absolute, meaning it prepends
'datacenter name/vm/' to the path if needed. If path already starts with
the datacenter name, nothing is added.
Eg: rest/of/path -> datacenter name/vm/rest/of/path
"""
return __prepend_datacenter_and_folder_type(folder_path, datacenter_name, folder_type='vm')


def format_folder_path_as_host_fq_path(folder_path, datacenter_name):
"""
Formats a host folder path so it is absolute, meaning it prepends
'datacenter name/vm/' to the path if needed. If path already starts with
the datacenter name, nothing is added.
Eg: rest/of/path -> datacenter name/host/rest/of/path
"""
return __prepend_datacenter_and_folder_type(folder_path, datacenter_name, folder_type='host')


def format_folder_path_as_network_fq_path(folder_path, datacenter_name):
"""
Formats a network folder path so it is absolute, meaning it prepends
'datacenter name/network/' to the path if needed. If path already starts with
the datacenter name, nothing is added.
Eg: rest/of/path -> datacenter name/network/rest/of/path
"""
return __prepend_datacenter_and_folder_type(folder_path, datacenter_name, folder_type='network')


def format_folder_path_as_datastore_fq_path(folder_path, datacenter_name):
"""
Formats a datastore folder path so it is absolute, meaning it prepends
'datacenter name/datastore/' to the path if needed. If path already starts with
the datacenter name, nothing is added.
Eg: rest/of/path -> datacenter name/datastore/rest/of/path
"""
return __prepend_datacenter_and_folder_type(folder_path, datacenter_name, folder_type='datastore')
Loading

0 comments on commit 969915d

Please sign in to comment.