mirror of https://github.com/ansible/ansible.git
add new module vmware_guest_sendkey
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…
Reference in New Issue