mirror of https://github.com/ansible/ansible.git
VMware: vmware_tools connection plugin (#55059)
* variable standardisation and cleanup - connection_address -> vmware_host - connection_username -> vmware_user - connection_password -> vmware_password - connection_verify_ssl -> validate_certs - connection_ignore_ssl_warnings -> silence_tls_warnings - ansible_vmware_tools_vm_path -> ansible_vmware_guest_path - standardize user/pass vars - fallback to default ansible conneciton vars - accept VMware standard env vars: - note lack of "become" support - add example usage - more reasonable default sleep interval - auto-silence tls warnings if validate_certs=false - get_option for executable - remove unsafe 'makedirs_safe' * executable: support env vars and inipull/55164/head
parent
7a1e2ef746
commit
2b8cef340e
@ -0,0 +1,493 @@
|
|||||||
|
# Copyright: (c) 2018, Deric Crago <deric.crago@gmail.com>
|
||||||
|
# Copyright: (c) 2018, Ansible Project
|
||||||
|
# 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 re
|
||||||
|
from os.path import exists, getsize
|
||||||
|
from socket import gaierror
|
||||||
|
from ssl import SSLEOFError, SSLError
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
REQUESTS_IMP_ERR = None
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
HAS_REQUESTS = True
|
||||||
|
except ImportError:
|
||||||
|
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||||
|
HAS_REQUESTS = False
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleConnectionFailure
|
||||||
|
from ansible.module_utils._text import to_bytes, to_native
|
||||||
|
from ansible.plugins.connection import ConnectionBase
|
||||||
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pyVim.connect import Disconnect, SmartConnect, SmartConnectNoSSL
|
||||||
|
from pyVmomi import vim
|
||||||
|
|
||||||
|
HAS_PYVMOMI = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_PYVMOMI = False
|
||||||
|
PYVMOMI_IMP_ERR = traceback.format_exc()
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
author: Deric Crago <deric.crago@gmail.com>
|
||||||
|
connection: vmware_tools
|
||||||
|
short_description: Execute tasks inside a VM via VMware Tools
|
||||||
|
description:
|
||||||
|
- Use VMware tools to run tasks in, or put/fetch files to guest operating systems running in VMware infrastructure.
|
||||||
|
- In case of Windows VMs, set C(ansible_shell_type) to C(powershell).
|
||||||
|
- Does not work with 'become'.
|
||||||
|
version_added: "2.8"
|
||||||
|
requirements:
|
||||||
|
- pyvmomi (Python library)
|
||||||
|
- requests (Python library)
|
||||||
|
options:
|
||||||
|
vmware_host:
|
||||||
|
description:
|
||||||
|
- FQDN or IP Address for the connection (vCenter or ESXi Host).
|
||||||
|
env:
|
||||||
|
- name: VI_SERVER
|
||||||
|
- name: VMWARE_HOST
|
||||||
|
vars:
|
||||||
|
- name: ansible_host
|
||||||
|
- name: ansible_vmware_host
|
||||||
|
required: True
|
||||||
|
vmware_user:
|
||||||
|
description:
|
||||||
|
- Username for the connection.
|
||||||
|
- "Requires the following permissions on the VM:
|
||||||
|
- VirtualMachine.GuestOperations.Execute
|
||||||
|
- VirtualMachine.GuestOperations.Modify
|
||||||
|
- VirtualMachine.GuestOperations.Query"
|
||||||
|
env:
|
||||||
|
- name: VI_USERNAME
|
||||||
|
- name: VMWARE_USER
|
||||||
|
vars:
|
||||||
|
- name: ansible_vmware_user
|
||||||
|
required: True
|
||||||
|
vmware_password:
|
||||||
|
description:
|
||||||
|
- Password for the connection.
|
||||||
|
env:
|
||||||
|
- name: VI_PASSWORD
|
||||||
|
- name: VMWARE_PASSWORD
|
||||||
|
vars:
|
||||||
|
- name: ansible_vmware_password
|
||||||
|
required: True
|
||||||
|
vmware_port:
|
||||||
|
description:
|
||||||
|
- Port for the connection.
|
||||||
|
env:
|
||||||
|
- name: VI_PORTNUMBER
|
||||||
|
- name: VMWARE_PORT
|
||||||
|
vars:
|
||||||
|
- name: ansible_port
|
||||||
|
- name: ansible_vmware_port
|
||||||
|
required: False
|
||||||
|
default: 443
|
||||||
|
validate_certs:
|
||||||
|
description:
|
||||||
|
- Verify SSL for the connection.
|
||||||
|
- "Note: This will validate certs for both C(vmware_host) and the ESXi host running the VM."
|
||||||
|
env:
|
||||||
|
- name: VMWARE_VALIDATE_CERTS
|
||||||
|
vars:
|
||||||
|
- name: ansible_vmware_validate_certs
|
||||||
|
default: True
|
||||||
|
type: bool
|
||||||
|
silence_tls_warnings:
|
||||||
|
description:
|
||||||
|
- Don't output warnings about insecure connections.
|
||||||
|
vars:
|
||||||
|
- name: ansible_vmware_silence_tls_warnings
|
||||||
|
default: True
|
||||||
|
type: bool
|
||||||
|
vm_path:
|
||||||
|
description:
|
||||||
|
- VM path absolute to the connection.
|
||||||
|
- "vCenter Example: C(Datacenter/vm/Discovered virtual machine/testVM)."
|
||||||
|
- "ESXi Host Example: C(ha-datacenter/vm/testVM)."
|
||||||
|
- Must include VM name, appended to 'folder' as would be passed to M(vmware_guest).
|
||||||
|
- Needs to include I(vm) between the Datacenter and the rest of the VM path.
|
||||||
|
- Datacenter default value for ESXi server is C(ha-datacenter).
|
||||||
|
- Folder I(vm) is not visible in the vSphere Web Client but necessary for VMware API to work.
|
||||||
|
vars:
|
||||||
|
- name: ansible_vmware_guest_path
|
||||||
|
required: True
|
||||||
|
vm_user:
|
||||||
|
description:
|
||||||
|
- VM username.
|
||||||
|
vars:
|
||||||
|
- name: ansible_user
|
||||||
|
- name: ansible_vmware_tools_user
|
||||||
|
required: True
|
||||||
|
vm_password:
|
||||||
|
description:
|
||||||
|
- Password for the user in guest operating system.
|
||||||
|
vars:
|
||||||
|
- name: ansible_password
|
||||||
|
- name: ansible_vmware_tools_password
|
||||||
|
required: True
|
||||||
|
exec_command_sleep_interval:
|
||||||
|
description:
|
||||||
|
- Time in seconds to sleep between execution of command.
|
||||||
|
vars:
|
||||||
|
- name: ansible_vmware_tools_exec_command_sleep_interval
|
||||||
|
default: 0.5
|
||||||
|
type: float
|
||||||
|
file_chunk_size:
|
||||||
|
description:
|
||||||
|
- File chunk size.
|
||||||
|
- "(Applicable when writing a file to disk, example: using the C(fetch) module.)"
|
||||||
|
vars:
|
||||||
|
- name: ansible_vmware_tools_file_chunk_size
|
||||||
|
default: 128
|
||||||
|
type: integer
|
||||||
|
executable:
|
||||||
|
description:
|
||||||
|
- shell to use for execution inside container
|
||||||
|
default: /bin/sh
|
||||||
|
ini:
|
||||||
|
- section: defaults
|
||||||
|
key: executable
|
||||||
|
env:
|
||||||
|
- name: ANSIBLE_EXECUTABLE
|
||||||
|
vars:
|
||||||
|
- name: ansible_executable
|
||||||
|
- name: ansible_vmware_tools_executable
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
# example vars.yml
|
||||||
|
---
|
||||||
|
ansible_connection: vmware_tools
|
||||||
|
|
||||||
|
ansible_vmware_host: vcenter.example.com
|
||||||
|
ansible_vmware_user: administrator@vsphere.local
|
||||||
|
ansible_vmware_password: Secr3tP4ssw0rd!12
|
||||||
|
ansible_vmware_validate_certs: no # default is yes
|
||||||
|
ansible_vmware_silence_tls_warnings: yes # default is yes
|
||||||
|
|
||||||
|
# vCenter Connection VM Path Example
|
||||||
|
ansible_vmware_guest_path: DATACENTER/vm/FOLDER/{{ inventory_hostname }}
|
||||||
|
# ESXi Connection VM Path Example
|
||||||
|
ansible_vmware_guest_path: ha-datacenter/vm/{{ inventory_hostname }}
|
||||||
|
|
||||||
|
ansible_vmware_tools_user: root
|
||||||
|
ansible_vmware_tools_password: MyR00tPassw0rD
|
||||||
|
|
||||||
|
# if the target VM guest is Windows set the 'ansible_shell_type' to 'powershell'
|
||||||
|
ansible_shell_type: powershell
|
||||||
|
|
||||||
|
|
||||||
|
# example playbook_linux.yml
|
||||||
|
---
|
||||||
|
- name: Test VMware Tools Connection Plugin for Linux
|
||||||
|
hosts: linux
|
||||||
|
tasks:
|
||||||
|
- command: whoami
|
||||||
|
|
||||||
|
- ping:
|
||||||
|
|
||||||
|
- copy:
|
||||||
|
src: foo
|
||||||
|
dest: /home/user/foo
|
||||||
|
|
||||||
|
- fetch:
|
||||||
|
src: /home/user/foo
|
||||||
|
dest: linux-foo
|
||||||
|
flat: yes
|
||||||
|
|
||||||
|
- file:
|
||||||
|
path: /home/user/foo
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
|
||||||
|
# example playbook_windows.yml
|
||||||
|
---
|
||||||
|
- name: Test VMware Tools Connection Plugin for Windows
|
||||||
|
hosts: windows
|
||||||
|
tasks:
|
||||||
|
- win_command: whoami
|
||||||
|
|
||||||
|
- win_ping:
|
||||||
|
|
||||||
|
- win_copy:
|
||||||
|
src: foo
|
||||||
|
dest: C:\Users\user\foo
|
||||||
|
|
||||||
|
- fetch:
|
||||||
|
src: C:\Users\user\foo
|
||||||
|
dest: windows-foo
|
||||||
|
flat: yes
|
||||||
|
|
||||||
|
- win_file:
|
||||||
|
path: C:\Users\user\foo
|
||||||
|
state: absent
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(ConnectionBase):
|
||||||
|
"""VMware Tools Connection."""
|
||||||
|
|
||||||
|
transport = "vmware_tools"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vmware_host(self):
|
||||||
|
"""Read-only property holding the connection address."""
|
||||||
|
return self.get_option("vmware_host")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def validate_certs(self):
|
||||||
|
"""Read-only property holding whether the connection should validate certs."""
|
||||||
|
return self.get_option("validate_certs")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def authManager(self):
|
||||||
|
"""Guest Authentication Manager."""
|
||||||
|
return self._si.content.guestOperationsManager.authManager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fileManager(self):
|
||||||
|
"""Guest File Manager."""
|
||||||
|
return self._si.content.guestOperationsManager.fileManager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def processManager(self):
|
||||||
|
"""Guest Process Manager."""
|
||||||
|
return self._si.content.guestOperationsManager.processManager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def windowsGuest(self):
|
||||||
|
"""Return if VM guest family is windows."""
|
||||||
|
return self.vm.guest.guestFamily == "windowsGuest"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""init."""
|
||||||
|
super(Connection, self).__init__(*args, **kwargs)
|
||||||
|
if hasattr(self, "_shell") and self._shell.SHELL_FAMILY == "powershell":
|
||||||
|
self.module_implementation_preferences = (".ps1", ".exe", "")
|
||||||
|
self.become_methods = ["runas"]
|
||||||
|
self.allow_executable = False
|
||||||
|
self.has_pipelining = True
|
||||||
|
self.allow_extras = True
|
||||||
|
|
||||||
|
def _establish_connection(self):
|
||||||
|
connection_kwargs = {
|
||||||
|
"host": self.vmware_host,
|
||||||
|
"user": self.get_option("vmware_user"),
|
||||||
|
"pwd": self.get_option("vmware_password"),
|
||||||
|
"port": self.get_option("vmware_port"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.validate_certs:
|
||||||
|
connect = SmartConnect
|
||||||
|
else:
|
||||||
|
if self.get_option("silence_tls_warnings"):
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
connect = SmartConnectNoSSL
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._si = connect(**connection_kwargs)
|
||||||
|
except SSLError:
|
||||||
|
raise AnsibleError("SSL Error: Certificate verification failed.")
|
||||||
|
except (gaierror, SSLEOFError):
|
||||||
|
raise AnsibleError("Connection Error: Unable to connect to '%s'." % to_native(connection_kwargs["host"]))
|
||||||
|
except vim.fault.InvalidLogin as e:
|
||||||
|
raise AnsibleError("Connection Login Error: %s" % to_native(e.msg))
|
||||||
|
|
||||||
|
def _establish_vm(self):
|
||||||
|
searchIndex = self._si.content.searchIndex
|
||||||
|
self.vm = searchIndex.FindByInventoryPath(self.get_option("vm_path"))
|
||||||
|
|
||||||
|
if self.vm is None:
|
||||||
|
raise AnsibleError("Unable to find VM by path '%s'" % to_native(self.get_option("vm_path")))
|
||||||
|
|
||||||
|
self.vm_auth = vim.NamePasswordAuthentication(
|
||||||
|
username=self.get_option("vm_user"), password=self.get_option("vm_password"), interactiveSession=False
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.authManager.ValidateCredentialsInGuest(vm=self.vm, auth=self.vm_auth)
|
||||||
|
except vim.fault.InvalidPowerState as e:
|
||||||
|
raise AnsibleError("VM Power State Error: %s" % to_native(e.msg))
|
||||||
|
except vim.fault.RestrictedVersion as e:
|
||||||
|
raise AnsibleError("Restricted Version Error: %s" % to_native(e.msg))
|
||||||
|
except vim.fault.GuestOperationsUnavailable as e:
|
||||||
|
raise AnsibleError("VM Guest Operations (VMware Tools) Error: %s" % to_native(e.msg))
|
||||||
|
except vim.fault.InvalidGuestLogin as e:
|
||||||
|
raise AnsibleError("VM Login Error: %s" % to_native(e.msg))
|
||||||
|
except vim.fault.NoPermission as e:
|
||||||
|
raise AnsibleConnectionFailure("No Permission Error: %s %s" % (to_native(e.msg), to_native(e.privilegeId)))
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
if not HAS_REQUESTS:
|
||||||
|
raise AnsibleError("%s : %s" % (missing_required_lib('requests'), REQUESTS_IMP_ERR))
|
||||||
|
|
||||||
|
if not HAS_PYVMOMI:
|
||||||
|
raise AnsibleError("%s : %s" % (missing_required_lib('PyVmomi'), PYVMOMI_IMP_ERR))
|
||||||
|
|
||||||
|
super(Connection, self)._connect()
|
||||||
|
|
||||||
|
if self.connected:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._establish_connection()
|
||||||
|
self._establish_vm()
|
||||||
|
|
||||||
|
self._connected = True
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close connection."""
|
||||||
|
super(Connection, self).close()
|
||||||
|
|
||||||
|
Disconnect(self._si)
|
||||||
|
self._connected = False
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the connection."""
|
||||||
|
super(Connection, self).reset()
|
||||||
|
|
||||||
|
self.close()
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
def create_temporary_file_in_guest(self, prefix="", suffix=""):
|
||||||
|
"""Create a temporary file in the VM."""
|
||||||
|
try:
|
||||||
|
return self.fileManager.CreateTemporaryFileInGuest(vm=self.vm, auth=self.vm_auth, prefix=prefix, suffix=suffix)
|
||||||
|
except vim.fault.NoPermission as e:
|
||||||
|
raise AnsibleError("No Permission Error: %s %s" % (to_native(e.msg), to_native(e.privilegeId)))
|
||||||
|
|
||||||
|
def _get_program_spec_program_path_and_arguments(self, cmd):
|
||||||
|
if self.windowsGuest:
|
||||||
|
cmd_parts = self._shell._encode_script(cmd, as_list=False, strict_mode=False, preserve_rc=False)
|
||||||
|
|
||||||
|
program_path = "cmd.exe"
|
||||||
|
arguments = "/c %s" % cmd_parts
|
||||||
|
else:
|
||||||
|
program_path = self.get_option("executable")
|
||||||
|
arguments = re.sub(r"^%s\s*" % program_path, "", cmd)
|
||||||
|
|
||||||
|
return program_path, arguments
|
||||||
|
|
||||||
|
def _get_guest_program_spec(self, cmd, stdout, stderr):
|
||||||
|
guest_program_spec = vim.GuestProgramSpec()
|
||||||
|
|
||||||
|
program_path, arguments = self._get_program_spec_program_path_and_arguments(cmd)
|
||||||
|
|
||||||
|
arguments += " 1> %s 2> %s" % (stdout, stderr)
|
||||||
|
|
||||||
|
guest_program_spec.programPath = program_path
|
||||||
|
guest_program_spec.arguments = arguments
|
||||||
|
|
||||||
|
return guest_program_spec
|
||||||
|
|
||||||
|
def _get_pid_info(self, pid):
|
||||||
|
try:
|
||||||
|
processes = self.processManager.ListProcessesInGuest(vm=self.vm, auth=self.vm_auth, pids=[pid])
|
||||||
|
except vim.fault.NoPermission as e:
|
||||||
|
raise AnsibleError("No Permission Error: %s %s" % (to_native(e.msg), to_native(e.privilegeId)))
|
||||||
|
return processes[0]
|
||||||
|
|
||||||
|
def _fix_url_for_hosts(self, url):
|
||||||
|
"""
|
||||||
|
Fix url if connection is a host.
|
||||||
|
|
||||||
|
The host part of the URL is returned as '*' if the hostname to be used is the name of the server to which the call was made. For example, if the call is
|
||||||
|
made to esx-svr-1.domain1.com, and the file is available for download from http://esx-svr-1.domain1.com/guestFile?id=1&token=1234, the URL returned may
|
||||||
|
be http://*/guestFile?id=1&token=1234. The client replaces the asterisk with the server name on which it invoked the call.
|
||||||
|
|
||||||
|
https://code.vmware.com/apis/358/vsphere#/doc/vim.vm.guest.FileManager.FileTransferInformation.html
|
||||||
|
"""
|
||||||
|
return url.replace("*", self.vmware_host)
|
||||||
|
|
||||||
|
def _fetch_file_from_vm(self, guestFilePath):
|
||||||
|
try:
|
||||||
|
fileTransferInformation = self.fileManager.InitiateFileTransferFromGuest(vm=self.vm, auth=self.vm_auth, guestFilePath=guestFilePath)
|
||||||
|
except vim.fault.NoPermission as e:
|
||||||
|
raise AnsibleError("No Permission Error: %s %s" % (to_native(e.msg), to_native(e.privilegeId)))
|
||||||
|
|
||||||
|
url = self._fix_url_for_hosts(fileTransferInformation.url)
|
||||||
|
response = requests.get(url, verify=self.validate_certs, stream=True)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise AnsibleError("Failed to fetch file")
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def delete_file_in_guest(self, filePath):
|
||||||
|
"""Delete file from VM."""
|
||||||
|
try:
|
||||||
|
self.fileManager.DeleteFileInGuest(vm=self.vm, auth=self.vm_auth, filePath=filePath)
|
||||||
|
except vim.fault.NoPermission as e:
|
||||||
|
raise AnsibleError("No Permission Error: %s %s" % (to_native(e.msg), to_native(e.privilegeId)))
|
||||||
|
|
||||||
|
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||||
|
"""Execute command."""
|
||||||
|
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||||
|
|
||||||
|
stdout = self.create_temporary_file_in_guest(suffix=".stdout")
|
||||||
|
stderr = self.create_temporary_file_in_guest(suffix=".stderr")
|
||||||
|
|
||||||
|
guest_program_spec = self._get_guest_program_spec(cmd, stdout, stderr)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pid = self.processManager.StartProgramInGuest(vm=self.vm, auth=self.vm_auth, spec=guest_program_spec)
|
||||||
|
except vim.fault.NoPermission as e:
|
||||||
|
raise AnsibleError("No Permission Error: %s %s" % (to_native(e.msg), to_native(e.privilegeId)))
|
||||||
|
except vim.fault.FileNotFound as e:
|
||||||
|
raise AnsibleError("StartProgramInGuest Error: %s" % to_native(e.msg))
|
||||||
|
|
||||||
|
pid_info = self._get_pid_info(pid)
|
||||||
|
|
||||||
|
while pid_info.endTime is None:
|
||||||
|
sleep(self.get_option("exec_command_sleep_interval"))
|
||||||
|
pid_info = self._get_pid_info(pid)
|
||||||
|
|
||||||
|
stdout_response = self._fetch_file_from_vm(stdout)
|
||||||
|
self.delete_file_in_guest(stdout)
|
||||||
|
|
||||||
|
stderr_response = self._fetch_file_from_vm(stderr)
|
||||||
|
self.delete_file_in_guest(stderr)
|
||||||
|
|
||||||
|
return pid_info.exitCode, stdout_response.text, stderr_response.text
|
||||||
|
|
||||||
|
def fetch_file(self, in_path, out_path):
|
||||||
|
"""Fetch file."""
|
||||||
|
super(Connection, self).fetch_file(in_path, out_path)
|
||||||
|
|
||||||
|
in_path_response = self._fetch_file_from_vm(in_path)
|
||||||
|
|
||||||
|
with open(out_path, "wb") as fd:
|
||||||
|
for chunk in in_path_response.iter_content(chunk_size=self.get_option("file_chunk_size")):
|
||||||
|
fd.write(chunk)
|
||||||
|
|
||||||
|
def put_file(self, in_path, out_path):
|
||||||
|
"""Put file."""
|
||||||
|
super(Connection, self).put_file(in_path, out_path)
|
||||||
|
|
||||||
|
if not exists(to_bytes(in_path, errors="surrogate_or_strict")):
|
||||||
|
raise AnsibleFileNotFound("file or module does not exist: '%s'" % to_native(in_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
put_url = self.fileManager.InitiateFileTransferToGuest(
|
||||||
|
vm=self.vm, auth=self.vm_auth, guestFilePath=out_path, fileAttributes=vim.GuestFileAttributes(), fileSize=getsize(in_path), overwrite=True
|
||||||
|
)
|
||||||
|
except vim.fault.NoPermission as e:
|
||||||
|
raise AnsibleError("No Permission Error: %s %s" % (to_native(e.msg), to_native(e.privilegeId)))
|
||||||
|
|
||||||
|
url = self._fix_url_for_hosts(put_url)
|
||||||
|
|
||||||
|
# file size of 'in_path' must be greater than 0
|
||||||
|
with open(in_path, "rb") as fd:
|
||||||
|
response = requests.put(url, verify=self.validate_certs, data=fd)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise AnsibleError("File transfer failed")
|
Loading…
Reference in New Issue