add new module vmware_guest_sendkey

pull/58920/head
Tomorrow9 6 years ago committed by Gonéri Le Bouder
parent 41b7b1c6e9
commit fcc0b71c74

@ -0,0 +1,354 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Ansible Project
# Copyright: (c) 2018, Diane Wang <dianew@vmware.com>
# 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_sendkey
short_description: Send USB HID codes to the Virtual Machine's keyboard.
description:
- This module is used to send keystrokes to given virtual machine.
- All parameters and VMware object names are case sensitive.
version_added: '2.9'
author:
- Diane Wang (@Tomorrow9) <dianew@vmware.com>
notes:
- Tested on vSphere 6.5 and 6.7
requirements:
- "python >= 2.6"
- PyVmomi
options:
name:
description:
- Name of the virtual machine.
- This is a required parameter, if parameter C(uuid) is not supplied.
uuid:
description:
- UUID of the instance to gather facts if known, this is VMware's unique identifier.
- This is a required parameter, if parameter C(name) is not supplied.
folder:
description:
- Destination folder, absolute or relative path to find an existing guest.
- This is a required parameter, only if multiple VMs are found with same name.
- The folder should include the datacenter. ESXi server's datacenter is ha-datacenter.
- 'Examples:'
- ' folder: /ha-datacenter/vm'
- ' folder: ha-datacenter/vm'
- ' folder: /datacenter1/vm'
- ' folder: datacenter1/vm'
- ' folder: /datacenter1/vm/folder1'
- ' folder: datacenter1/vm/folder1'
- ' folder: /folder1/datacenter1/vm'
- ' folder: folder1/datacenter1/vm'
- ' folder: /folder1/datacenter1/vm/folder2'
cluster:
description:
- The name of cluster where the virtual machine is running.
- This is a required parameter, if C(esxi_hostname) is not set.
- C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
esxi_hostname:
description:
- The ESXi hostname where the virtual machine is running.
- This is a required parameter, if C(cluster) is not set.
- C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
datacenter:
description:
- The datacenter name to which virtual machine belongs to.
string_send:
description:
- The string will be sent to the virtual machine.
- This string can contain valid special character, alphabet and digit on the keyboard.
keys_send:
description:
- The list of the keys will be sent to the virtual machine.
- 'Valid values are C(ENTER), C(ESC), C(BACKSPACE), C(TAB), C(SPACE), C(CAPSLOCK), C(DELETE), C(CTRL_ALT_DEL),
C(CTRL_C) and C(F1) to C(F12), C(RIGHTARROW), C(LEFTARROW), C(DOWNARROW), C(UPARROW).'
- If both C(keys_send) and C(string_send) are specified, keys in C(keys_send) list will be sent in front of the C(string_send).
extends_documentation_fragment: vmware.documentation
'''
EXAMPLES = '''
- name: send list of keys to virtual machine
vmware_guest_sendkey:
validate_certs: no
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
datacenter: "{{ datacenter_name }}"
folder: "{{ folder_name }}"
name: "{{ vm_name }}"
keys_send:
- TAB
- TAB
- ENTER
delegate_to: localhost
register: keys_num_sent
- name: send a string to virtual machine
vmware_guest_sendkey:
validate_certs: no
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
datacenter: "{{ datacenter_name }}"
folder: "{{ folder_name }}"
name: "{{ vm_name }}"
string_send: "user_logon"
delegate_to: localhost
register: keys_num_sent
'''
RETURN = """
sendkey_facts:
description: display the keys and the number of keys sent to the virtual machine
returned: always
type: dict
sample: {
"virtual_machine": "test_vm",
"keys_send": [
"SPACE",
"DOWNARROW",
"DOWNARROW",
"ENTER"
],
"string_send": null,
"keys_send_number": 4,
"returned_keys_send_number": 4,
}
"""
try:
from pyVmomi import vim, vmodl
except ImportError:
pass
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec
class PyVmomiHelper(PyVmomi):
def __init__(self, module):
super(PyVmomiHelper, self).__init__(module)
self.change_detected = False
self.usb_scan_code_spec = vim.UsbScanCodeSpec()
self.num_keys_send = 0
# HID usage tables https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
# define valid characters and keys value, hex_code, key value and key modifier
self.keys_hid_code = [
(('a', 'A'), '0x04', [('a', []), ('A', ['LEFTSHIFT'])]),
(('b', 'B'), '0x05', [('b', []), ('B', ['LEFTSHIFT'])]),
(('c', 'C'), '0x06', [('c', []), ('C', ['LEFTSHIFT'])]),
(('d', 'D'), '0x07', [('d', []), ('D', ['LEFTSHIFT'])]),
(('e', 'E'), '0x08', [('e', []), ('E', ['LEFTSHIFT'])]),
(('f', 'F'), '0x09', [('f', []), ('F', ['LEFTSHIFT'])]),
(('g', 'G'), '0x0a', [('g', []), ('G', ['LEFTSHIFT'])]),
(('h', 'H'), '0x0b', [('h', []), ('H', ['LEFTSHIFT'])]),
(('i', 'I'), '0x0c', [('i', []), ('I', ['LEFTSHIFT'])]),
(('j', 'J'), '0x0d', [('j', []), ('J', ['LEFTSHIFT'])]),
(('k', 'K'), '0x0e', [('k', []), ('K', ['LEFTSHIFT'])]),
(('l', 'L'), '0x0f', [('l', []), ('L', ['LEFTSHIFT'])]),
(('m', 'M'), '0x10', [('m', []), ('M', ['LEFTSHIFT'])]),
(('n', 'N'), '0x11', [('n', []), ('N', ['LEFTSHIFT'])]),
(('o', 'O'), '0x12', [('o', []), ('O', ['LEFTSHIFT'])]),
(('p', 'P'), '0x13', [('p', []), ('P', ['LEFTSHIFT'])]),
(('q', 'Q'), '0x14', [('q', []), ('Q', ['LEFTSHIFT'])]),
(('r', 'R'), '0x15', [('r', []), ('R', ['LEFTSHIFT'])]),
(('s', 'S'), '0x16', [('s', []), ('S', ['LEFTSHIFT'])]),
(('t', 'T'), '0x17', [('t', []), ('T', ['LEFTSHIFT'])]),
(('u', 'U'), '0x18', [('u', []), ('U', ['LEFTSHIFT'])]),
(('v', 'V'), '0x19', [('v', []), ('V', ['LEFTSHIFT'])]),
(('w', 'W'), '0x1a', [('w', []), ('W', ['LEFTSHIFT'])]),
(('x', 'X'), '0x1b', [('x', []), ('X', ['LEFTSHIFT'])]),
(('y', 'Y'), '0x1c', [('y', []), ('Y', ['LEFTSHIFT'])]),
(('z', 'Z'), '0x1d', [('z', []), ('Z', ['LEFTSHIFT'])]),
(('1', '!'), '0x1e', [('1', []), ('!', ['LEFTSHIFT'])]),
(('2', '@'), '0x1f', [('2', []), ('@', ['LEFTSHIFT'])]),
(('3', '#'), '0x20', [('3', []), ('#', ['LEFTSHIFT'])]),
(('4', '$'), '0x21', [('4', []), ('$', ['LEFTSHIFT'])]),
(('5', '%'), '0x22', [('5', []), ('%', ['LEFTSHIFT'])]),
(('6', '^'), '0x23', [('6', []), ('^', ['LEFTSHIFT'])]),
(('7', '&'), '0x24', [('7', []), ('&', ['LEFTSHIFT'])]),
(('8', '*'), '0x25', [('8', []), ('*', ['LEFTSHIFT'])]),
(('9', '('), '0x26', [('9', []), ('(', ['LEFTSHIFT'])]),
(('0', ')'), '0x27', [('0', []), (')', ['LEFTSHIFT'])]),
(('-', '_'), '0x2d', [('-', []), ('_', ['LEFTSHIFT'])]),
(('=', '+'), '0x2e', [('=', []), ('+', ['LEFTSHIFT'])]),
(('[', '{'), '0x2f', [('[', []), ('{', ['LEFTSHIFT'])]),
((']', '}'), '0x30', [(']', []), ('}', ['LEFTSHIFT'])]),
(('\\', '|'), '0x31', [('\\', []), ('|', ['LEFTSHIFT'])]),
((';', ':'), '0x33', [(';', []), (':', ['LEFTSHIFT'])]),
(('\'', '"'), '0x34', [('\'', []), ('"', ['LEFTSHIFT'])]),
(('`', '~'), '0x35', [('`', []), ('~', ['LEFTSHIFT'])]),
((',', '<'), '0x36', [(',', []), ('<', ['LEFTSHIFT'])]),
(('.', '>'), '0x37', [('.', []), ('>', ['LEFTSHIFT'])]),
(('/', '?'), '0x38', [('/', []), ('?', ['LEFTSHIFT'])]),
('ENTER', '0x28', [('', [])]),
('ESC', '0x29', [('', [])]),
('BACKSPACE', '0x2a', [('', [])]),
('TAB', '0x2b', [('', [])]),
('SPACE', '0x2c', [(' ', [])]),
('CAPSLOCK', '0x39', [('', [])]),
('F1', '0x3a', [('', [])]),
('F2', '0x3b', [('', [])]),
('F3', '0x3c', [('', [])]),
('F4', '0x3d', [('', [])]),
('F5', '0x3e', [('', [])]),
('F6', '0x3f', [('', [])]),
('F7', '0x40', [('', [])]),
('F8', '0x41', [('', [])]),
('F9', '0x42', [('', [])]),
('F10', '0x43', [('', [])]),
('F11', '0x44', [('', [])]),
('F12', '0x45', [('', [])]),
('DELETE', '0x4c', [('', [])]),
('CTRL_ALT_DEL', '0x4c', [('', ['CTRL', 'ALT'])]),
('CTRL_C', '0x06', [('', ['CTRL'])]),
('RIGHTARROW', '0x4f', [('', [])]),
('LEFTARROW', '0x50', [('', [])]),
('DOWNARROW', '0x51', [('', [])]),
('UPARROW', '0x52', [('', [])]),
]
@staticmethod
def hid_to_hex(hid_code):
return int(hid_code, 16) << 16 | 0o0007
def get_hid_from_key(self, key):
if key == ' ':
return '0x2c', []
for keys_name, key_code, keys_value in self.keys_hid_code:
if isinstance(keys_name, tuple):
for keys in keys_value:
if key == keys[0]:
return key_code, keys[1]
else:
if key == keys_name:
return key_code, keys_value[0][1]
def get_key_event(self, hid_code, modifiers):
key_event = vim.UsbScanCodeSpecKeyEvent()
key_modifier = vim.UsbScanCodeSpecModifierType()
key_modifier.leftAlt = False
key_modifier.leftControl = False
key_modifier.leftGui = False
key_modifier.leftShift = False
key_modifier.rightAlt = False
key_modifier.rightControl = False
key_modifier.rightGui = False
key_modifier.rightShift = False
# rightShift, rightControl, rightAlt, leftGui, rightGui are not used
if "LEFTSHIFT" in modifiers:
key_modifier.leftShift = True
if "CTRL" in modifiers:
key_modifier.leftControl = True
if "ALT" in modifiers:
key_modifier.leftAlt = True
key_event.modifiers = key_modifier
key_event.usbHidCode = self.hid_to_hex(hid_code)
return key_event
def get_sendkey_facts(self, vm_obj, returned_value=0):
sendkey_facts = dict()
if vm_obj is not None:
sendkey_facts = dict(
virtual_machine=vm_obj.name,
keys_send=self.params['keys_send'],
string_send=self.params['string_send'],
keys_send_number=self.num_keys_send,
returned_keys_send_number=returned_value,
)
return sendkey_facts
def send_key_to_vm(self, vm_obj):
key_event = None
num_keys_returned = 0
if self.params['keys_send']:
for specified_key in self.params['keys_send']:
key_found = False
for keys in self.keys_hid_code:
if (isinstance(keys[0], tuple) and specified_key in keys[0]) or \
(not isinstance(keys[0], tuple) and specified_key == keys[0]):
hid_code, modifiers = self.get_hid_from_key(specified_key)
key_event = self.get_key_event(hid_code, modifiers)
self.usb_scan_code_spec.keyEvents.append(key_event)
self.num_keys_send += 1
key_found = True
break
if not key_found:
self.module.fail_json(msg="keys_send parameter: '%s' in %s not supported."
% (specified_key, self.params['keys_send']))
if self.params['string_send']:
for char in self.params['string_send']:
key_found = False
for keys in self.keys_hid_code:
if (isinstance(keys[0], tuple) and char in keys[0]) or char == ' ':
hid_code, modifiers = self.get_hid_from_key(char)
key_event = self.get_key_event(hid_code, modifiers)
self.usb_scan_code_spec.keyEvents.append(key_event)
self.num_keys_send += 1
key_found = True
break
if not key_found:
self.module.fail_json(msg="string_send parameter: '%s' contains char: '%s' not supported."
% (self.params['string_send'], char))
if self.usb_scan_code_spec.keyEvents:
try:
num_keys_returned = vm_obj.PutUsbScanCodes(self.usb_scan_code_spec)
self.change_detected = True
except vmodl.RuntimeFault as e:
self.module.fail_json(msg="Failed to send key %s to virtual machine due to %s" % (key_event, to_native(e.msg)))
sendkey_facts = self.get_sendkey_facts(vm_obj, num_keys_returned)
if num_keys_returned != self.num_keys_send:
results = {'changed': self.change_detected, 'failed': True, 'sendkey_facts': sendkey_facts}
else:
results = {'changed': self.change_detected, 'failed': False, 'sendkey_facts': sendkey_facts}
return results
def main():
argument_spec = vmware_argument_spec()
argument_spec.update(
name=dict(type='str'),
uuid=dict(type='str'),
folder=dict(type='str'),
datacenter=dict(type='str'),
esxi_hostname=dict(type='str'),
cluster=dict(type='str'),
keys_send=dict(type='list', default=[]),
string_send=dict(type='str')
)
module = AnsibleModule(argument_spec=argument_spec, required_one_of=[['name', 'uuid']])
pyv = PyVmomiHelper(module)
vm = pyv.get_vm()
if not vm:
module.fail_json(msg='Unable to find the specified virtual machine uuid: %s, name: %s '
% ((module.params.get('uuid')), (module.params.get('name'))))
result = pyv.send_key_to_vm(vm)
if result['failed']:
module.fail_json(**result)
else:
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -0,0 +1,3 @@
cloud/vcenter
shippable/vcenter/group1
needs/target/prepare_vmware_tests

@ -0,0 +1,45 @@
# Test code for the vmware_guest_sendkey module
# Copyright: (c) 2017, Diane Wang (Tomorrow9) <dianew@vmware.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- when: vcsim is not defined
block:
- import_role:
name: prepare_vmware_tests
vars:
setup_attach_host: true
setup_datastore: true
setup_virtualmachines: true
- name: send keys to virtual machine
vmware_guest_sendkey:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ infra.vm_list[0] }}"
keys_send:
- DOWNARROW
- DOWNARROW
- ENTER
register: send_key
- debug: var=send_key
- name: assert the keys were sent to VM
assert:
that:
- "send_key.changed == true"
- name: send string to virtual machine
vmware_guest_sendkey:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ infra.vm_list[0] }}"
string_send: "test-user"
register: send_string
- debug: var=send_string
- name: assert the string was sent to VM
assert:
that:
- "send_string.changed == true"
Loading…
Cancel
Save