summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames E. King III <jking@apache.org>2019-02-25 06:03:40 -0500
committeransibot <ansibot@users.noreply.github.com>2019-02-25 06:03:40 -0500
commitf2495ef0d320678a84e4c8f1da199e9dde4e9a5a (patch)
treefc61d8033c2321564e76976f0dcc36aa2cd3a03b
parent7f50f467fee95be0fc290af5e4cb6a80a37ec1c8 (diff)
downloadansible-f2495ef0d320678a84e4c8f1da199e9dde4e9a5a.tar.gz
Add ability to get vmware_guest_facts using vsphere schema output (#47446)
-rw-r--r--lib/ansible/module_utils/vmware.py108
-rw-r--r--lib/ansible/modules/cloud/vmware/vmware_guest_facts.py62
-rw-r--r--test/integration/targets/vmware_guest_facts/tasks/main.yml41
3 files changed, 188 insertions, 23 deletions
diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py
index cae4343810..7559ea98d6 100644
--- a/lib/ansible/module_utils/vmware.py
+++ b/lib/ansible/module_utils/vmware.py
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Joseph Callen <jcallen () csc.com>
# Copyright: (c) 2018, Ansible Project
+# Copyright: (c) 2018, James E. King III (@jeking3) <jking@apache.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import atexit
+import ansible.module_utils.common._collections_compat as collections_compat
+import json
import os
import re
import ssl
@@ -27,11 +30,13 @@ except ImportError:
PYVMOMI_IMP_ERR = None
try:
from pyVim import connect
- from pyVmomi import vim, vmodl
+ from pyVmomi import vim, vmodl, VmomiSupport
HAS_PYVMOMI = True
+ HAS_PYVMOMIJSON = hasattr(VmomiSupport, 'VmomiJSONEncoder')
except ImportError:
PYVMOMI_IMP_ERR = traceback.format_exc()
HAS_PYVMOMI = False
+ HAS_PYVMOMIJSON = False
from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.six import integer_types, iteritems, string_types, raise_from
@@ -1274,3 +1279,104 @@ class PyVmomi(object):
return f
self.module.fail_json(msg="No vmdk file found for path specified [%s]" % vmdk_path)
+
+ #
+ # Conversion to JSON
+ #
+
+ def _deepmerge(self, d, u):
+ """
+ Deep merges u into d.
+
+ Credit:
+ https://bit.ly/2EDOs1B (stackoverflow question 3232943)
+ License:
+ cc-by-sa 3.0 (https://creativecommons.org/licenses/by-sa/3.0/)
+ Changes:
+ using collections_compat for compatibility
+
+ Args:
+ - d (dict): dict to merge into
+ - u (dict): dict to merge into d
+
+ Returns:
+ dict, with u merged into d
+ """
+ for k, v in iteritems(u):
+ if isinstance(v, collections_compat.Mapping):
+ d[k] = self._deepmerge(d.get(k, {}), v)
+ else:
+ d[k] = v
+ return d
+
+ def _extract(self, data, remainder):
+ """
+ This is used to break down dotted properties for extraction.
+
+ Args:
+ - data (dict): result of _jsonify on a property
+ - remainder: the remainder of the dotted property to select
+
+ Return:
+ dict
+ """
+ result = dict()
+ if '.' not in remainder:
+ result[remainder] = data[remainder]
+ return result
+ key, remainder = remainder.split('.', 1)
+ result[key] = self._extract(data[key], remainder)
+ return result
+
+ def _jsonify(self, obj):
+ """
+ Convert an object from pyVmomi into JSON.
+
+ Args:
+ - obj (object): vim object
+
+ Return:
+ dict
+ """
+ return json.loads(json.dumps(obj, cls=VmomiSupport.VmomiJSONEncoder,
+ sort_keys=True, strip_dynamic=True))
+
+ def to_json(self, obj, properties=None):
+ """
+ Convert a vSphere (pyVmomi) Object into JSON. This is a deep
+ transformation. The list of properties is optional - if not
+ provided then all properties are deeply converted. The resulting
+ JSON is sorted to improve human readability.
+
+ Requires upstream support from pyVmomi > 6.7.1
+ (https://github.com/vmware/pyvmomi/pull/732)
+
+ Args:
+ - obj (object): vim object
+ - properties (list, optional): list of properties following
+ the property collector specification, for example:
+ ["config.hardware.memoryMB", "name", "overallStatus"]
+ default is a complete object dump, which can be large
+
+ Return:
+ dict
+ """
+ if not HAS_PYVMOMIJSON:
+ self.module.fail_json(msg='The installed version of pyvmomi lacks JSON output support; need pyvmomi>6.7.1')
+
+ result = dict()
+ if properties:
+ for prop in properties:
+ try:
+ if '.' in prop:
+ key, remainder = prop.split('.', 1)
+ tmp = dict()
+ tmp[key] = self._extract(self._jsonify(getattr(obj, key)), remainder)
+ self._deepmerge(result, tmp)
+ else:
+ result[prop] = self._jsonify(getattr(obj, prop))
+ except (AttributeError, KeyError):
+ self.module.fail_json(msg="Property '{0}' not found.".format(prop))
+ else:
+ result = self._jsonify(obj)
+ return result
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_facts.py b/lib/ansible/modules/cloud/vmware/vmware_guest_facts.py
index ea0facbba1..a9d6bc9749 100644
--- a/lib/ansible/modules/cloud/vmware/vmware_guest_facts.py
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest_facts.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
#
# This module is also sponsored by E.T.A.I. (www.etai.fr)
+# Copyright (C) 2018 James E. King III (@jeking3) <jking@apache.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -25,7 +26,7 @@ version_added: 2.3
author:
- Loic Blot (@nerzhul) <loic.blot@unix-experience.fr>
notes:
- - Tested on vSphere 5.5
+ - Tested on vSphere 5.5, 6.7
requirements:
- "python >= 2.6"
- PyVmomi
@@ -79,6 +80,32 @@ options:
default: 'no'
type: bool
version_added: '2.8'
+ schema:
+ description:
+ - Specify the output schema desired.
+ - The 'summary' output schema is the legacy output from the module
+ - The 'vsphere' output schema is the vSphere API class definition
+ which requires pyvmomi>6.7.1
+ choices: ['summary', 'vsphere']
+ default: 'summary'
+ type: str
+ version_added: '2.8'
+ properties:
+ description:
+ - Specify the properties to retrieve.
+ - If not specified, all properties are retrieved (deeply).
+ - Results are returned in a structure identical to the vsphere API.
+ - 'Example:'
+ - ' properties: ['
+ - ' "config.hardware.memoryMB",'
+ - ' "config.hardware.numCPU",'
+ - ' "guest.disk",'
+ - ' "overallStatus"'
+ - ' ]'
+ - Only valid when C(schema) is C(vsphere).
+ type: list
+ required: False
+ version_added: '2.8'
extends_documentation_fragment: vmware.documentation
'''
@@ -93,6 +120,19 @@ EXAMPLES = '''
uuid: 421e4592-c069-924d-ce20-7e7533fab926
delegate_to: localhost
register: facts
+
+- name: Gather some facts from a guest using the vSphere API output schema
+ vmware_guest_facts:
+ hostname: "{{ vcenter_hostname }}"
+ username: "{{ vcenter_username }}"
+ password: "{{ vcenter_password }}"
+ validate_certs: no
+ datacenter: "{{ datacenter_name }}"
+ name: "{{ vm_name }}"
+ schema: "vsphere"
+ properties: ["config.hardware.memoryMB", "guest.disk", "overallStatus"]
+ delegate_to: localhost
+ register: facts
'''
RETURN = """
@@ -168,11 +208,6 @@ except ImportError:
HAS_VCLOUD = False
-class PyVmomiHelper(PyVmomi):
- def __init__(self, module):
- super(PyVmomiHelper, self).__init__(module)
-
-
class VmwareTag(VmwareRestClient):
def __init__(self, module):
super(VmwareTag, self).__init__(module)
@@ -189,7 +224,9 @@ def main():
use_instance_uuid=dict(type='bool', default=False),
folder=dict(type='str'),
datacenter=dict(type='str', required=True),
- tags=dict(type='bool', default=False)
+ tags=dict(type='bool', default=False),
+ schema=dict(type='str', choices=['summary', 'vsphere'], default='summary'),
+ properties=dict(type='list')
)
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=[['name', 'uuid']])
@@ -199,14 +236,21 @@ def main():
# so we should leave the input folder path unmodified
module.params['folder'] = module.params['folder'].rstrip('/')
- pyv = PyVmomiHelper(module)
+ if module.params['schema'] != 'vsphere' and module.params.get('properties'):
+ module.fail_json(msg="The option 'properties' is only valid when the schema is 'vsphere'")
+
+ pyv = PyVmomi(module)
# Check if the VM exists before continuing
vm = pyv.get_vm()
# VM already exists
if vm:
try:
- instance = pyv.gather_facts(vm)
+ if module.params['schema'] == 'summary':
+ instance = pyv.gather_facts(vm)
+ else:
+ instance = pyv.to_json(vm, module.params['properties'])
+
if module.params.get('tags'):
if not HAS_VCLOUD:
module.fail_json(msg="Unable to find 'vCloud Suite SDK' Python library which is required."
diff --git a/test/integration/targets/vmware_guest_facts/tasks/main.yml b/test/integration/targets/vmware_guest_facts/tasks/main.yml
index ade4d6283c..bdba6d4a59 100644
--- a/test/integration/targets/vmware_guest_facts/tasks/main.yml
+++ b/test/integration/targets/vmware_guest_facts/tasks/main.yml
@@ -1,7 +1,8 @@
# Test code for the vmware_guest_facts module.
# Copyright: (c) 2017, Abhijeet Kasurde <akasurde@redhat.com>
+# Copyright: (c) 2018, James E. King III (@jeking3) <jking@apache.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
+---
- name: store the vcenter container ip
set_fact:
vcsim: "{{ lookup('env', 'vcenter_host') }}"
@@ -62,7 +63,7 @@
folder: "{{ vm1 | dirname }}"
register: guest_facts_0001
-- debug: msg="{{ guest_facts_0001 }}"
+- debug: var=guest_facts_0001
- assert:
that:
@@ -98,7 +99,25 @@
uuid: "{{ vm1_uuid }}"
register: guest_facts_0002
-- debug: msg="{{ guest_facts_0002 }}"
+- debug: var=guest_facts_0002
+
+- name: "Get specific details about virtual machines using the vsphere output schema"
+ vmware_guest_facts:
+ validate_certs: False
+ hostname: "{{ vcsim }}"
+ username: "{{ vcsim_instance['json']['username'] }}"
+ password: "{{ vcsim_instance['json']['password'] }}"
+ datacenter: "{{ dc1 | basename }}"
+ uuid: "{{ vm1_uuid }}"
+ schema: vsphere
+ properties:
+ - config.hardware.memoryMB
+ - guest
+ - name
+ - summary.runtime.connectionState
+ register: guest_facts_0002b
+
+- debug: var=guest_facts_0002b
- assert:
that:
@@ -106,14 +125,10 @@
- "guest_facts_0002['instance']['hw_product_uuid'] is defined"
- "guest_facts_0002['instance']['hw_product_uuid'] == vm1_uuid"
- "guest_facts_0002['instance']['hw_cores_per_socket'] is defined"
- - "guest_facts_0001['instance']['hw_datastores'] is defined"
- - "guest_facts_0001['instance']['hw_esxi_host'] == h1 | basename"
- - "guest_facts_0001['instance']['hw_files'] is defined"
- - "guest_facts_0001['instance']['hw_guest_ha_state'] is defined"
- - "guest_facts_0001['instance']['hw_is_template'] is defined"
- - "guest_facts_0001['instance']['hw_folder'] is defined"
- - "guest_facts_0001['instance']['guest_question'] is defined"
- - "guest_facts_0001['instance']['guest_consolidation_needed'] is defined"
+ - "guest_facts_0002b['instance']['config']['hardware']['memoryMB'] is defined"
+ - "guest_facts_0002b['instance']['config']['hardware']['numCoresPerSocket'] is not defined"
+ - "guest_facts_0002b['instance']['guest']['toolsVersion'] is defined"
+ - "guest_facts_0002b['instance']['overallStatus'] is not defined"
# Testcase 0003: Get details about virtual machines without snapshots using UUID
- name: get empty list of snapshots from virtual machine using UUID
@@ -126,7 +141,7 @@
uuid: "{{ vm1_uuid }}"
register: guest_facts_0003
-- debug: msg="{{ guest_facts_0003 }}"
+- debug: var=guest_facts_0003
- assert:
that:
@@ -169,7 +184,7 @@
# uuid: "{{ vm1_uuid }}"
# register: guest_facts_0004
-#- debug: msg="{{ guest_facts_0004 }}"
+#- debug: var=guest_facts_0004
#- assert:
# that: