From 778b789c84f2b2748013b686aff97c7a18602c3b Mon Sep 17 00:00:00 2001 From: Armin Ranjbar Daemi Date: Fri, 7 Sep 2018 12:30:37 +0200 Subject: [PATCH] VMware: New module vmware_guest_vnc (#36282) --- lib/ansible/module_utils/vmware.py | 12 + .../modules/cloud/vmware/vmware_guest_vnc.py | 240 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 lib/ansible/modules/cloud/vmware/vmware_guest_vnc.py diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py index 6a8be7eabba..5812656efc5 100644 --- a/lib/ansible/module_utils/vmware.py +++ b/lib/ansible/module_utils/vmware.py @@ -295,6 +295,7 @@ def gather_vm_facts(content, vm): 'customvalues': {}, 'snapshots': [], 'current_snapshot': None, + 'vnc': {}, } # facts that may or may not exist @@ -398,6 +399,8 @@ def gather_vm_facts(content, vm): if 'snapshots' in snapshot_facts: facts['snapshots'] = snapshot_facts['snapshots'] facts['current_snapshot'] = snapshot_facts['current_snapshot'] + + facts['vnc'] = get_vnc_extraconfig(vm) return facts @@ -444,6 +447,15 @@ def list_snapshots(vm): return result +def get_vnc_extraconfig(vm): + result = {} + for opts in vm.config.extraConfig: + for optkeyname in ['enabled', 'ip', 'port', 'password']: + if opts.key.lower() == "remotedisplay.vnc." + optkeyname: + result[optkeyname] = opts.value + return result + + def vmware_argument_spec(): return dict( hostname=dict(type='str', diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_vnc.py b/lib/ansible/modules/cloud/vmware/vmware_guest_vnc.py new file mode 100644 index 00000000000..74d899d51b6 --- /dev/null +++ b/lib/ansible/modules/cloud/vmware/vmware_guest_vnc.py @@ -0,0 +1,240 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Armin Ranjbar Daemi +# 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 + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: vmware_guest_vnc +short_description: Manages VNC remote display on virtual machines in vCenter +description: + - This module can be used to enable and disable VNC remote display on virtual machine. +version_added: 2.8 +author: + - Armin Ranjbar Daemi (@rmin) +requirements: + - python >= 2.6 + - PyVmomi +options: + datacenter: + description: + - Destination datacenter for the deploy operation. + - This parameter is case sensitive. + default: ha-datacenter + state: + description: + - Set the state of VNC on virtual machine. + choices: [present, absent] + default: present + required: false + name: + description: + - Name of the virtual machine to work with. + - Virtual machine names in vCenter are not necessarily unique, which may be problematic, see C(name_match). + required: false + name_match: + description: + - If multiple virtual machines matching the name, use the first or last found. + default: first + choices: [first, last] + required: false + uuid: + description: + - UUID of the instance to manage if known, this is VMware's unique identifier. + - This is required, if C(name) is not supplied. + required: false + folder: + description: + - Destination folder, absolute or relative path to find an existing guest. + - The folder should include the datacenter. ESX's datacenter is ha-datacenter + required: false + vnc_ip: + description: + - Sets an IP for VNC on virtual machine. + - This is required only when I(state) is set to present and will be ignored if I(state) is absent. + default: 0.0.0.0 + required: false + vnc_port: + description: + - The port that VNC listens on. Usually a number between 5900 and 7000 depending on your config. + - This is required only when I(state) is set to present and will be ignored if I(state) is absent. + default: 0 + required: false + vnc_password: + description: + - Sets a password for VNC on virtual machine. + - This is required only when I(state) is set to present and will be ignored if I(state) is absent. + default: "" + required: false +extends_documentation_fragment: vmware.documentation +''' + +EXAMPLES = ''' +- name: Enable VNC remote display on the VM + vmware_guest_vnc: + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + validate_certs: no + folder: /mydatacenter/vm + name: testvm1 + vnc_port: 5990 + vnc_password: vNc5ecr3t + datacenter: "{{ datacenter_name }}" + state: present + delegate_to: localhost + register: vnc_result + +- name: Disable VNC remote display on the VM + vmware_guest_vnc: + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + validate_certs: no + datacenter: "{{ datacenter_name }}" + uuid: 32074771-7d6b-699a-66a8-2d9cf8236fff + state: absent + delegate_to: localhost + register: vnc_result +''' + +RETURN = ''' +changed: + description: If anything changed on VM's extraConfig. + returned: always + type: bool +failed: + description: If changes failed. + returned: always + type: bool +instance: + description: Dictionary describing the VM, including VNC info. + returned: On success in both I(state) + type: dict +''' + +try: + from pyVmomi import vim +except ImportError: + pass + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec, get_vnc_extraconfig, wait_for_task, gather_vm_facts, TaskError +from ansible.module_utils._text import to_native + + +def set_vnc_extraconfig(content, vm, enabled, ip, port, password): + result = dict( + changed=False, + failed=False, + ) + # set new values + key_prefix = "remotedisplay.vnc." + new_values = dict() + for key in ['enabled', 'ip', 'port', 'password']: + new_values[key_prefix + key] = "" + if enabled: + new_values[key_prefix + "enabled"] = "true" + new_values[key_prefix + "password"] = str(password).strip() + new_values[key_prefix + "ip"] = str(ip).strip() + new_values[key_prefix + "port"] = str(port).strip() + + # get current vnc config + current_values = get_vnc_extraconfig(vm) + # check if any value is changed + reconfig_vm = False + for key, val in new_values.items(): + key = key.replace(key_prefix, "") + current_value = current_values.get(key, "") + # enabled is not case-sensitive + if key == "enabled": + current_value = current_value.lower() + val = val.lower() + if current_value != val: + reconfig_vm = True + if not reconfig_vm: + return result + # reconfigure vm + spec = vim.vm.ConfigSpec() + spec.extraConfig = [] + for key, val in new_values.items(): + opt = vim.option.OptionValue() + opt.key = key + opt.value = val + spec.extraConfig.append(opt) + task = vm.ReconfigVM_Task(spec) + try: + wait_for_task(task) + except TaskError as task_err: + result['failed'] = True + result['msg'] = to_native(task_err) + + if task.info.state == 'error': + result['failed'] = True + result['msg'] = task.info.error.msg + else: + result['changed'] = True + result['instance'] = gather_vm_facts(content, vm) + return result + + +def main(): + argument_spec = vmware_argument_spec() + argument_spec.update( + state=dict(type='str', default='present', choices=['present', 'absent']), + name=dict(type='str'), + name_match=dict(type='str', choices=['first', 'last'], default='first'), + uuid=dict(type='str'), + folder=dict(type='str'), + vnc_ip=dict(type='str', default='0.0.0.0'), + vnc_port=dict(type='int', default=0), + vnc_password=dict(type='str', default='', no_log=True), + datacenter=dict(type='str', default='ha-datacenter') + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[ + ['name', 'uuid'] + ], + mutually_exclusive=[ + ['name', 'uuid'] + ] + ) + + result = dict(changed=False, failed=False) + + pyv = PyVmomi(module) + vm = pyv.get_vm() + if vm: + result = set_vnc_extraconfig( + pyv.content, + vm, + (module.params['state'] == "present"), + module.params['vnc_ip'], + module.params['vnc_port'], + module.params['vnc_password'] + ) + else: + module.fail_json(msg="Unable to set VNC config for non-existing virtual machine : '%s'" % (module.params.get('uuid') or + module.params.get('name'))) + + if result.get('failed') is True: + module.fail_json(**result) + + module.exit_json(**result) + + +if __name__ == "__main__": + main()