diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70ac56ba5a..cba745b112 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -220,6 +220,8 @@ Ansible Changes By Release
- tempfile
- tower:
* tower_organization
+- vmware_guest_facts
+- vmware_guest_snapshot
- web_infrastructure
* jenkins_script
- windows:
diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py
index e681aa68d5..3f34d5621f 100644
--- a/lib/ansible/module_utils/vmware.py
+++ b/lib/ansible/module_utils/vmware.py
@@ -61,6 +61,7 @@ def find_dvspg_by_name(dv_switch, portgroup_name):
return None
+
def find_entity_child_by_path(content, entityRootFolder, path):
entity = entityRootFolder
@@ -88,6 +89,7 @@ def find_cluster_by_name_datacenter(datacenter, cluster_name):
return folder
return None
+
def find_cluster_by_name(content, cluster_name, datacenter=None):
if datacenter:
@@ -102,6 +104,7 @@ def find_cluster_by_name(content, cluster_name, datacenter=None):
return None
+
def find_datacenter_by_name(content, datacenter_name):
datacenters = get_all_objs(content, [vim.Datacenter])
@@ -138,6 +141,7 @@ def find_hostsystem_by_name(content, hostname):
return host
return None
+
def find_vm_by_id(content, vm_id, vm_id_type="vm_name", datacenter=None, cluster=None):
""" UUID is unique to a VM, every other id returns the first match. """
si = content.searchIndex
@@ -147,7 +151,7 @@ def find_vm_by_id(content, vm_id, vm_id_type="vm_name", datacenter=None, cluster
vm = si.FindByDnsName(datacenter=datacenter, dnsName=vm_id, vmSearch=True)
elif vm_id_type == 'inventory_path':
vm = si.FindByInventoryPath(inventoryPath=vm_id)
- if type(vm) != type(vim.VirtualMachine):
+ if isinstance(vm, vim.VirtualMachine):
vm = None
elif vm_id_type == 'uuid':
vm = si.FindByUuid(datacenter=datacenter, instanceUuid=vm_id, vmSearch=True)
@@ -166,7 +170,7 @@ def find_vm_by_id(content, vm_id, vm_id_type="vm_name", datacenter=None, cluster
def find_vm_by_name(content, vm_name, folder=None, recurse=True):
- vms = get_all_objs(content, [vim.VirtualMachine], folder, recurse=True)
+ vms = get_all_objs(content, [vim.VirtualMachine], folder, recurse=recurse)
for vm in vms:
if vm.name == vm_name:
return vm
@@ -181,6 +185,113 @@ def find_host_portgroup_by_name(host, portgroup_name):
return None
+def gather_vm_facts(content, vm):
+ """ Gather facts from vim.VirtualMachine object. """
+ facts = {
+ 'module_hw': True,
+ 'hw_name': vm.config.name,
+ 'hw_power_status': vm.summary.runtime.powerState,
+ 'hw_guest_full_name': vm.summary.guest.guestFullName,
+ 'hw_guest_id': vm.summary.guest.guestId,
+ 'hw_product_uuid': vm.config.uuid,
+ 'hw_processor_count': vm.config.hardware.numCPU,
+ 'hw_memtotal_mb': vm.config.hardware.memoryMB,
+ 'hw_interfaces': [],
+ 'ipv4': None,
+ 'ipv6': None,
+ 'annotation': vm.config.annotation,
+ 'customvalues': {},
+ 'snapshots': [],
+ 'current_snapshot': None,
+ }
+
+ cfm = content.customFieldsManager
+ # Resolve custom values
+ for value_obj in vm.summary.customValue:
+ kn = value_obj.key
+ if cfm is not None and cfm.field:
+ for f in cfm.field:
+ if f.key == value_obj.key:
+ kn = f.name
+ # Exit the loop immediately, we found it
+ break
+
+ facts['customvalues'][kn] = value_obj.value
+
+ net_dict = {}
+ for device in vm.guest.net:
+ net_dict[device.macAddress] = list(device.ipAddress)
+
+ for k, v in iteritems(net_dict):
+ for ipaddress in v:
+ if ipaddress:
+ if '::' in ipaddress:
+ facts['ipv6'] = ipaddress
+ else:
+ facts['ipv4'] = ipaddress
+
+ ethernet_idx = 0
+ for idx, entry in enumerate(vm.config.hardware.device):
+ if not hasattr(entry, 'macAddress'):
+ continue
+
+ factname = 'hw_eth' + str(ethernet_idx)
+ facts[factname] = {
+ 'addresstype': entry.addressType,
+ 'label': entry.deviceInfo.label,
+ 'macaddress': entry.macAddress,
+ 'ipaddresses': net_dict.get(entry.macAddress, None),
+ 'macaddress_dash': entry.macAddress.replace(':', '-'),
+ 'summary': entry.deviceInfo.summary,
+ }
+ facts['hw_interfaces'].append('eth' + str(ethernet_idx))
+ ethernet_idx += 1
+
+ snapshot_facts = list_snapshots(vm)
+ if 'snapshots' in snapshot_facts:
+ facts['snapshots'] = snapshot_facts['snapshots']
+ facts['current_snapshot'] = snapshot_facts['current_snapshot']
+ return facts
+
+
+def deserialize_snapshot_obj(obj):
+ return {'id': obj.id,
+ 'name': obj.name,
+ 'description': obj.description,
+ 'creation_time': obj.createTime,
+ 'state': obj.state}
+
+
+def list_snapshots_recursively(snapshots):
+ snapshot_data = []
+ for snapshot in snapshots:
+ snapshot_data.append(deserialize_snapshot_obj(snapshot))
+ snapshot_data = snapshot_data + list_snapshots_recursively(snapshot.childSnapshotList)
+ return snapshot_data
+
+
+def get_current_snap_obj(snapshots, snapob):
+ snap_obj = []
+ for snapshot in snapshots:
+ if snapshot.snapshot == snapob:
+ snap_obj.append(snapshot)
+ snap_obj = snap_obj + get_current_snap_obj(snapshot.childSnapshotList, snapob)
+ return snap_obj
+
+
+def list_snapshots(vm):
+ result = {}
+ if vm.snapshot is None:
+ return result
+
+ result['snapshots'] = list_snapshots_recursively(vm.snapshot.rootSnapshotList)
+ current_snapref = vm.snapshot.currentSnapshot
+ current_snap_obj = get_current_snap_obj(vm.snapshot.rootSnapshotList, current_snapref)
+ result['current_snapshot'] = deserialize_snapshot_obj(current_snap_obj[0])
+
+ return result
+
+
def vmware_argument_spec():
return dict(
@@ -199,7 +310,8 @@ def connect_to_api(module, disconnect_atexit=True):
validate_certs = module.params['validate_certs']
if validate_certs and not hasattr(ssl, 'SSLContext'):
- module.fail_json(msg='pyVim does not support changing verification mode with python < 2.7.9. Either update python or or use validate_certs=false')
+ module.fail_json(msg='pyVim does not support changing verification mode with python < 2.7.9. Either update '
+ 'python or or use validate_certs=false')
try:
service_instance = connect.SmartConnect(host=hostname, user=username, pwd=password)
@@ -224,6 +336,7 @@ def connect_to_api(module, disconnect_atexit=True):
atexit.register(connect.Disconnect, service_instance)
return service_instance.RetrieveContent()
+
def get_all_objs(content, vimtype, folder=None, recurse=True):
if not folder:
folder = content.rootFolder
@@ -234,6 +347,7 @@ def get_all_objs(content, vimtype, folder=None, recurse=True):
obj.update({managed_object_ref: managed_object_ref.name})
return obj
+
def fetch_file_from_guest(content, vm, username, password, src, dest):
""" Use VMWare's filemanager api to fetch a file over http """
@@ -282,6 +396,7 @@ def fetch_file_from_guest(content, vm, username, password, src, dest):
return result
+
def push_file_to_guest(content, vm, username, password, src, dest, overwrite=True):
""" Use VMWare's filemanager api to fetch a file over http """
@@ -331,6 +446,7 @@ def push_file_to_guest(content, vm, username, password, src, dest, overwrite=Tru
return result
+
def run_command_in_guest(content, vm, username, password, program_path, program_args, program_cwd, program_env):
result = {'failed': False}
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest.py b/lib/ansible/modules/cloud/vmware/vmware_guest.py
index a8871080e6..c04f6bbbc5 100644
--- a/lib/ansible/modules/cloud/vmware/vmware_guest.py
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest.py
@@ -132,11 +132,6 @@ options:
- 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"
- snapshot_op:
- description:
- - A key, value pair of snapshot operation types and their additional required parameters.
- - Beware that this functionality will disappear in v2.3 and move into module C(vmware_guest_snapshot)
- version_added: "2.3"
customization:
description:
- "Parameters to customize template"
@@ -163,17 +158,6 @@ extends_documentation_fragment: vmware.documentation
'''
EXAMPLES = '''
-# Gather facts only
- - name: gather the VM facts
- vmware_guest:
- hostname: 192.168.1.209
- username: administrator@vsphere.local
- password: vmware
- validate_certs: no
- esxi_hostname: 192.168.1.117
- uuid: 421e4592-c069-924d-ce20-7e7533fab926
- register: facts
-
# Create a VM from a template
- name: create the VM
vmware_guest:
@@ -276,68 +260,6 @@ EXAMPLES = '''
password: vmware
uuid: 421e4592-c069-924d-ce20-7e7533fab926
state: absent
-
-### Snapshot Operations
-
-# BEWARE: This functionality will move into vmware_guest_snapshot before release !
-
-# Create snapshot
- - vmware_guest:
- hostname: 192.168.1.209
- username: administrator@vsphere.local
- password: vmware
- name: dummy_vm
- snapshot_op:
- op_type: create
- name: snap1
- description: snap1_description
-
-# Remove a snapshot
- - vmware_guest:
- hostname: 192.168.1.209
- username: administrator@vsphere.local
- password: vmware
- name: dummy_vm
- snapshot_op:
- op_type: remove
- name: snap1
-
-# Revert to a snapshot
- - vmware_guest:
- hostname: 192.168.1.209
- username: administrator@vsphere.local
- password: vmware
- name: dummy_vm
- snapshot_op:
- op_type: revert
- name: snap1
-
-# List all snapshots of a VM
- - vmware_guest:
- hostname: 192.168.1.209
- username: administrator@vsphere.local
- password: vmware
- name: dummy_vm
- snapshot_op:
- op_type: list_all
-
-# List current snapshot of a VM
- - vmware_guest:
- hostname: 192.168.1.209
- username: administrator@vsphere.local
- password: vmware
- name: dummy_vm
- snapshot_op:
- op_type: list_current
-
-# Remove all snapshots of a VM
- - vmware_guest:
- hostname: 192.168.1.209
- username: administrator@vsphere.local
- password: vmware
- name: dummy_vm
- snapshot_op:
- op_type: remove_all
'''
RETURN = """
@@ -356,7 +278,7 @@ 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
+from ansible.module_utils.vmware import get_all_objs, connect_to_api, gather_vm_facts
try:
import json
@@ -611,66 +533,7 @@ class PyVmomiHelper(object):
return result
def gather_facts(self, vm):
- """ Gather facts from vim.VirtualMachine object. """
- facts = {
- 'module_hw': True,
- 'hw_name': vm.config.name,
- 'hw_power_status': vm.summary.runtime.powerState,
- 'hw_guest_full_name': vm.summary.guest.guestFullName,
- 'hw_guest_id': vm.summary.guest.guestId,
- 'hw_product_uuid': vm.config.uuid,
- 'hw_processor_count': vm.config.hardware.numCPU,
- 'hw_memtotal_mb': vm.config.hardware.memoryMB,
- 'hw_interfaces': [],
- 'ipv4': None,
- 'ipv6': None,
- 'annotation': vm.config.annotation,
- 'customvalues': {},
- }
-
- cfm = self.content.customFieldsManager
- # Resolve customvalues
- for value_obj in vm.summary.customValue:
- kn = value_obj.key
- if cfm is not None and cfm.field:
- for f in cfm.field:
- if f.key == value_obj.key:
- kn = f.name
- # Exit the loop immediately, we found it
- break
-
- facts['customvalues'][kn] = value_obj.value
-
- netDict = {}
- for device in vm.guest.net:
- netDict[device.macAddress] = list(device.ipAddress)
-
- for k, v in iteritems(netDict):
- for ipaddress in v:
- if ipaddress:
- if '::' in ipaddress:
- facts['ipv6'] = ipaddress
- else:
- facts['ipv4'] = ipaddress
-
- ethernet_idx = 0
- for idx, entry in enumerate(vm.config.hardware.device):
- if not hasattr(entry, 'macAddress'):
- continue
-
- factname = 'hw_eth' + str(ethernet_idx)
- facts[factname] = {
- 'addresstype': entry.addressType,
- 'label': entry.deviceInfo.label,
- 'macaddress': entry.macAddress,
- 'ipaddresses': netDict.get(entry.macAddress, None),
- 'macaddress_dash': entry.macAddress.replace(':', '-'),
- 'summary': entry.deviceInfo.summary,
- }
- facts['hw_interfaces'].append('eth' + str(ethernet_idx))
- ethernet_idx += 1
-
- return facts
+ return gather_vm_facts(self.content, vm)
def remove_vm(self, vm):
# https://www.vmware.com/support/developer/converter-sdk/conv60_apireference/vim.ManagedEntity.html#destroy
@@ -714,6 +577,7 @@ class PyVmomiHelper(object):
elif vm_creation and not self.should_deploy_from_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 []
@@ -1283,7 +1147,7 @@ class PyVmomiHelper(object):
return {'changed': change_applied, 'failed': True, 'msg': task.info.error.msg}
# Rename VM
- if self.params['uuid'] and self.params['name'] and self.params['name'] != vm.config.name:
+ if self.params['uuid'] and self.params['name'] and self.params['name'] != self.current_vm_obj.config.name:
task = self.current_vm_obj.Rename_Task(self.params['name'])
self.wait_for_task(task)
change_applied = True
@@ -1323,110 +1187,6 @@ class PyVmomiHelper(object):
return facts
- def list_snapshots_recursively(self, snapshots):
- snapshot_data = []
- for snapshot in snapshots:
- snap_text = 'Id: %s; Name: %s; Description: %s; CreateTime: %s; State: %s' % (snapshot.id, snapshot.name,
- snapshot.description,
- snapshot.createTime,
- snapshot.state)
- snapshot_data.append(snap_text)
- snapshot_data = snapshot_data + self.list_snapshots_recursively(snapshot.childSnapshotList)
- return snapshot_data
-
- def get_snapshots_by_name_recursively(self, snapshots, snapname):
- snap_obj = []
- for snapshot in snapshots:
- if snapshot.name == snapname:
- snap_obj.append(snapshot)
- else:
- snap_obj = snap_obj + self.get_snapshots_by_name_recursively(snapshot.childSnapshotList, snapname)
- return snap_obj
-
- def get_current_snap_obj(self, snapshots, snapob):
- snap_obj = []
- for snapshot in snapshots:
- if snapshot.snapshot == snapob:
- snap_obj.append(snapshot)
- snap_obj = snap_obj + self.get_current_snap_obj(snapshot.childSnapshotList, snapob)
- return snap_obj
-
- def snapshot_vm(self, vm, guest, snapshot_op):
- """ To perform snapshot operations create/remove/revert/list_all/list_current/remove_all """
-
- snapshot_op_name = None
- try:
- snapshot_op_name = snapshot_op['op_type']
- except KeyError:
- self.module.fail_json(msg="Specify op_type - create/remove/revert/list_all/list_current/remove_all")
-
- task = None
- result = {}
-
- if snapshot_op_name not in ['create', 'remove', 'revert', 'list_all', 'list_current', 'remove_all']:
- self.module.fail_json(msg="Specify op_type - create/remove/revert/list_all/list_current/remove_all")
-
- if snapshot_op_name != 'create' and vm.snapshot is None:
- self.module.exit_json(msg="VM - %s doesn't have any snapshots" % guest)
-
- if snapshot_op_name == 'create':
- try:
- snapname = snapshot_op['name']
- except KeyError:
- self.module.fail_json(msg="specify name & description(optional) to create a snapshot")
-
- if 'description' in snapshot_op:
- snapdesc = snapshot_op['description']
- else:
- snapdesc = ''
-
- dumpMemory = False
- quiesce = False
- task = vm.CreateSnapshot(snapname, snapdesc, dumpMemory, quiesce)
-
- elif snapshot_op_name in ['remove', 'revert']:
- try:
- snapname = snapshot_op['name']
- except KeyError:
- self.module.fail_json(msg="specify snapshot name")
-
- snap_obj = self.get_snapshots_by_name_recursively(vm.snapshot.rootSnapshotList, snapname)
-
- # if len(snap_obj) is 0; then no snapshots with specified name
- if len(snap_obj) == 1:
- snap_obj = snap_obj[0].snapshot
- if snapshot_op_name == 'remove':
- task = snap_obj.RemoveSnapshot_Task(True)
- else:
- task = snap_obj.RevertToSnapshot_Task()
- else:
- self.module.exit_json(
- msg="Couldn't find any snapshots with specified name: %s on VM: %s" % (snapname, guest))
-
- elif snapshot_op_name == 'list_all':
- snapshot_data = self.list_snapshots_recursively(vm.snapshot.rootSnapshotList)
- result['snapshot_data'] = snapshot_data
-
- elif snapshot_op_name == 'list_current':
- current_snapref = vm.snapshot.currentSnapshot
- current_snap_obj = self.get_current_snap_obj(vm.snapshot.rootSnapshotList, current_snapref)
- result['current_snapshot'] = 'Id: %s; Name: %s; Description: %s; CreateTime: %s; State: %s' % (
- current_snap_obj[0].id,
- current_snap_obj[0].name, current_snap_obj[0].description, current_snap_obj[0].createTime,
- current_snap_obj[0].state)
-
- elif snapshot_op_name == 'remove_all':
- task = vm.RemoveAllSnapshots()
-
- if task:
- self.wait_for_task(task)
- if task.info.state == 'error':
- result = {'changed': False, 'failed': True, 'msg': task.info.error.msg}
- else:
- result = {'changed': True, 'failed': False}
-
- return result
-
def get_obj(content, vimtype, name):
"""
@@ -1473,7 +1233,6 @@ def main():
'absent',
'restarted',
'suspended',
- 'gatherfacts',
],
default='present'),
validate_certs=dict(required=False, type='bool', default=True),
@@ -1483,7 +1242,6 @@ def main():
customvalues=dict(required=False, type='list', default=[]),
name=dict(required=True, type='str'),
name_match=dict(required=False, type='str', default='first'),
- snapshot_op=dict(required=False, type='dict', default={}),
uuid=dict(required=False, type='str'),
folder=dict(required=False, type='str', default='/vm'),
guest_id=dict(required=False, type='str', default=None),
@@ -1538,15 +1296,6 @@ def main():
result["changed"] = True
if not tmp_result["failed"]:
result["failed"] = False
- elif module.params['state'] == 'gatherfacts':
- # Run for facts only
- try:
- module.exit_json(instance=pyv.gather_facts(vm))
- except Exception:
- e = get_exception()
- module.fail_json(msg="Fact gather failed with exception %s" % e)
- elif module.params['snapshot_op']:
- result = pyv.snapshot_vm(vm, module.params['name'], module.params['snapshot_op'])
else:
# This should not happen
assert False
@@ -1555,8 +1304,6 @@ def main():
if module.params['state'] in ['poweredon', 'poweredoff', 'present', 'restarted', 'suspended']:
# Create it ...
result = pyv.deploy_vm()
- elif module.params['state'] == 'gatherfacts':
- module.fail_json(msg="Unable to gather facts for non-existing VM %(name)s" % module.params)
if 'failed' not in result:
result['failed'] = False
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_facts.py b/lib/ansible/modules/cloud/vmware/vmware_guest_facts.py
new file mode 100644
index 0000000000..2608c3951e
--- /dev/null
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest_facts.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This module is also sponsored by E.T.A.I. (www.etai.fr)
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: vmware_guest_facts
+short_description: Gather facts about a single VM
+description:
+ - Gather facts about a single VM on a VMware ESX cluster
+version_added: 2.3
+author:
+ - Loic Blot (@nerzhul)
+notes:
+ - Tested on vSphere 5.5
+requirements:
+ - "python >= 2.6"
+ - PyVmomi
+options:
+ name:
+ description:
+ - Name of the VM to work with
+ required: True
+ name_match:
+ description:
+ - If multiple VMs matching the name, use the first or last found
+ default: 'first'
+ choices: ['first', 'last']
+ uuid:
+ description:
+ - UUID of the instance to manage if known, this is VMware's unique identifier.
+ - This is required if name is not supplied.
+ folder:
+ description:
+ - Destination folder, absolute path to find an existing guest.
+ - This is required if name is supplied.
+ datacenter:
+ description:
+ - Destination datacenter for the deploy operation
+ required: True
+extends_documentation_fragment: vmware.documentation
+'''
+
+EXAMPLES = '''
+# Gather facts
+ - name: gather the VM facts
+ vmware_guest_facts:
+ hostname: 192.168.1.209
+ username: administrator@vsphere.local
+ password: vmware
+ validate_certs: no
+ uuid: 421e4592-c069-924d-ce20-7e7533fab926
+ register: facts
+'''
+
+RETURN = """
+instance:
+ descripton: metadata about the 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.vmware import connect_to_api, find_vm_by_id, gather_vm_facts
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+HAS_PYVMOMI = False
+try:
+ import pyVmomi
+ from pyVmomi import vim
+
+ HAS_PYVMOMI = True
+except ImportError:
+ pass
+
+
+class PyVmomiHelper(object):
+ def __init__(self, module):
+ if not HAS_PYVMOMI:
+ module.fail_json(msg='pyvmomi module required')
+
+ self.module = module
+ self.params = module.params
+ self.content = connect_to_api(self.module)
+
+ def getvm(self, name=None, uuid=None, folder=None):
+ vm = None
+
+ if uuid:
+ vm = find_vm_by_id(self.content, vm_id=uuid, vm_id_type="uuid")
+ elif folder:
+ # Build the absolute folder path to pass into the search method
+ if not self.params['folder'].startswith('/'):
+ self.module.fail_json(msg="Folder %(folder)s needs to be an absolute path, starting with '/'." % self.params)
+ searchpath = '%(datacenter)s%(folder)s' % self.params
+
+ # get all objects for this path ...
+ f_obj = self.content.searchIndex.FindByInventoryPath(searchpath)
+ if f_obj:
+ if isinstance(f_obj, vim.Datacenter):
+ f_obj = f_obj.vmFolder
+ for c_obj in f_obj.childEntity:
+ if not isinstance(c_obj, vim.VirtualMachine):
+ continue
+ if c_obj.name == name:
+ vm = c_obj
+ if self.params['name_match'] == 'first':
+ break
+
+ return vm
+
+ def gather_facts(self, vm):
+ return gather_vm_facts(self.content, vm)
+
+
+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')
+ ),
+ validate_certs=dict(required=False, type='bool', default=True),
+ name=dict(required=True, type='str'),
+ name_match=dict(required=False, type='str', default='first'),
+ uuid=dict(required=False, type='str'),
+ folder=dict(required=False, type='str', default='/vm'),
+ datacenter=dict(required=True, type='str'),
+ ),
+ )
+
+ # Prepend /vm if it was missing from the folder path, also strip trailing slashes
+ if not module.params['folder'].startswith('/vm') and module.params['folder'].startswith('/'):
+ module.params['folder'] = '/vm%(folder)s' % module.params
+ 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 already exists
+ if vm:
+ try:
+ module.exit_json(instance=pyv.gather_facts(vm))
+ except Exception:
+ e = get_exception()
+ module.fail_json(msg="Fact gather failed with exception %s" % e)
+ else:
+ module.fail_json(msg="Unable to gather facts for non-existing VM %(name)s" % module.params)
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_snapshot.py b/lib/ansible/modules/cloud/vmware/vmware_guest_snapshot.py
new file mode 100644
index 0000000000..17423c1d67
--- /dev/null
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest_snapshot.py
@@ -0,0 +1,312 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This module is also sponsored by E.T.A.I. (www.etai.fr)
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: vmware_guest_snapshot
+short_description: Manages virtual machines snapshots in vcenter
+description:
+ - Create virtual machines snapshots
+version_added: 2.3
+author:
+ - James Tanner (@jctanner)
+ - Loic Blot (@nerzhul)
+notes:
+ - Tested on vSphere 5.5
+requirements:
+ - "python >= 2.6"
+ - PyVmomi
+options:
+ state:
+ description:
+ - Manage snapshots attached to a specific virtual machine.
+ required: True
+ choices: ['present', 'absent', 'revert', 'remove_all']
+ name:
+ description:
+ - Name of the VM to work with
+ required: True
+ name_match:
+ description:
+ - If multiple VMs matching the name, use the first or last found
+ default: 'first'
+ choices: ['first', 'last']
+ uuid:
+ description:
+ - UUID of the instance to manage if known, this is VMware's unique identifier.
+ - This is required if name is not supplied.
+ folder:
+ description:
+ - Define instance folder location.
+ datacenter:
+ description:
+ - Destination datacenter for the deploy operation
+ required: True
+ snapshot_name:
+ description:
+ - Sets the snapshot name to manage.
+ - This param is required only if state is not C(remove_all)
+ description:
+ description:
+ - Define an arbitrary description to attach to snapshot.
+extends_documentation_fragment: vmware.documentation
+'''
+
+EXAMPLES = '''
+ - name: Create snapshot
+ vmware_guest_snapshot:
+ hostname: 192.168.1.209
+ username: administrator@vsphere.local
+ password: vmware
+ name: dummy_vm
+ state: present
+ snapshot_name: snap1
+ description: snap1_description
+
+ - name: Remove a snapshot
+ vmware_guest_snapshot:
+ hostname: 192.168.1.209
+ username: administrator@vsphere.local
+ password: vmware
+ name: dummy_vm
+ state: remove
+ snapshot_name: snap1
+
+ - name: Revert to a snapshot
+ vmware_guest_snapshot:
+ hostname: 192.168.1.209
+ username: administrator@vsphere.local
+ password: vmware
+ name: dummy_vm
+ state: revert
+ snapshot_name: snap1
+
+ - name: Remove all snapshots of a VM
+ vmware_guest_snapshot:
+ hostname: 192.168.1.209
+ username: administrator@vsphere.local
+ password: vmware
+ name: dummy_vm
+ state: remove_all
+'''
+
+RETURN = """
+instance:
+ descripton: 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.vmware import connect_to_api
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+HAS_PYVMOMI = False
+try:
+ import pyVmomi
+ from pyVmomi import vim
+
+ HAS_PYVMOMI = True
+except ImportError:
+ pass
+
+
+class PyVmomiHelper(object):
+ def __init__(self, module):
+ if not HAS_PYVMOMI:
+ module.fail_json(msg='pyvmomi module required')
+
+ self.module = module
+ self.params = module.params
+ self.si = None
+ self.content = connect_to_api(self.module)
+ self.change_detected = False
+
+ def getvm(self, name=None, uuid=None, folder=None):
+
+ # https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.SearchIndex.html
+ # self.si.content.searchIndex.FindByInventoryPath('DC1/vm/test_folder')
+
+ vm = None
+
+ if uuid:
+ vm = self.content.searchIndex.FindByUuid(uuid=uuid, vmSearch=True)
+ elif folder:
+ # Build the absolute folder path to pass into the search method
+ if not self.params['folder'].startswith('/'):
+ self.module.fail_json(msg="Folder %(folder)s needs to be an absolute path, starting with '/'." % self.params)
+ searchpath = '%(datacenter)s%(folder)s' % self.params
+
+ # get all objects for this path ...
+ f_obj = self.content.searchIndex.FindByInventoryPath(searchpath)
+ if f_obj:
+ if isinstance(f_obj, vim.Datacenter):
+ f_obj = f_obj.vmFolder
+ for c_obj in f_obj.childEntity:
+ if not isinstance(c_obj, vim.VirtualMachine):
+ continue
+ if c_obj.name == name:
+ vm = c_obj
+ if self.params['name_match'] == 'first':
+ break
+
+ return vm
+
+ @staticmethod
+ def wait_for_task(task):
+ # 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']:
+ time.sleep(1)
+
+ def get_snapshots_by_name_recursively(self, snapshots, snapname):
+ snap_obj = []
+ for snapshot in snapshots:
+ if snapshot.name == snapname:
+ snap_obj.append(snapshot)
+ else:
+ snap_obj = snap_obj + self.get_snapshots_by_name_recursively(snapshot.childSnapshotList, snapname)
+ return snap_obj
+
+ def snapshot_vm(self, vm):
+ dump_memory = False
+ quiesce = False
+ return vm.CreateSnapshot(self.module.params["snapshot_name"],
+ self.module.params["description"],
+ dump_memory,
+ quiesce)
+
+ def remove_or_revert_snapshot(self, vm):
+ if vm.snapshot is None:
+ self.module.exit_json(msg="VM - %s doesn't have any snapshots" % self.module.params["name"])
+
+ snap_obj = self.get_snapshots_by_name_recursively(vm.snapshot.rootSnapshotList,
+ self.module.params["snapshot_name"])
+ task = None
+ if len(snap_obj) == 1:
+ snap_obj = snap_obj[0].snapshot
+ if self.module.params["state"] == "absent":
+ task = snap_obj.RemoveSnapshot_Task(True)
+ elif self.module.params["state"] == "revert":
+ task = snap_obj.RevertToSnapshot_Task()
+ else:
+ self.module.exit_json(
+ msg="Couldn't find any snapshots with specified name: %s on VM: %s" %
+ (self.module.params["snapshot_name"], self.module.params["name"]))
+
+ return task
+
+ def apply_snapshot_op(self, vm):
+ result = {}
+ if self.module.params["state"] == "present":
+ task = self.snapshot_vm(vm)
+ elif self.module.params["state"] in ["absent", "revert"]:
+ task = self.remove_or_revert_snapshot(vm)
+ elif self.module.params["state"] == "remove_all":
+ task = vm.RemoveAllSnapshots()
+ else:
+ # This should not happen
+ assert False
+
+ if task:
+ self.wait_for_task(task)
+ if task.info.state == 'error':
+ result = {'changed': False, 'failed': True, 'msg': task.info.error.msg}
+ else:
+ result = {'changed': True, 'failed': False}
+
+ return result
+
+
+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=['present', 'absent', 'revert', 'remove_all'],
+ default='present'),
+ validate_certs=dict(required=False, type='bool', default=True),
+ name=dict(required=True, type='str'),
+ name_match=dict(required=False, type='str', default='first'),
+ uuid=dict(required=False, type='str'),
+ folder=dict(required=False, type='str', default='/vm'),
+ datacenter=dict(required=True, type='str'),
+ snapshot_name=dict(required=False, type='str'),
+ description=dict(required=False, type='str', default=''),
+ ),
+ )
+
+ # Prepend /vm if it was missing from the folder path, also strip trailing slashes
+ if not module.params['folder'].startswith('/vm') and module.params['folder'].startswith('/'):
+ module.params['folder'] = '/vm%(folder)s' % module.params
+ 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'])
+
+ if not vm:
+ module.fail_json(msg="Unable to manage snapshots for non-existing VM %(name)s" % module.params)
+
+ if not module.params['snapshot_name'] and module.params['state'] != 'remove_all':
+ module.fail_json(msg="snapshot_name param is required when state is '%(state)s'" % module.params)
+
+ result = pyv.apply_snapshot_op(vm)
+
+ if 'failed' not in result:
+ result['failed'] = False
+
+ if result['failed']:
+ module.fail_json(**result)
+ else:
+ module.exit_json(**result)
+
+if __name__ == '__main__':
+ main()
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
index 6cfb723eba..a592c42fb5 100644
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -25,7 +25,6 @@ lib/ansible/module_utils/f5.py
lib/ansible/module_utils/facts.py
lib/ansible/module_utils/known_hosts.py
lib/ansible/module_utils/mysql.py
-lib/ansible/module_utils/vmware.py
lib/ansible/modules/cloud/amazon/_ec2_vpc.py
lib/ansible/modules/cloud/amazon/aws_kms.py
lib/ansible/modules/cloud/amazon/cloudformation.py