-
Notifications
You must be signed in to change notification settings - Fork 23.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
402 additions
and
0 deletions.
There are no files selected for viewing
354 changes: 354 additions & 0 deletions
354
lib/ansible/modules/cloud/vmware/vmware_guest_sendkey.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
cloud/vcenter | ||
shippable/vcenter/group1 | ||
needs/target/prepare_vmware_tests |
Oops, something went wrong.