VMware: vsphere_copy: ability to target an esxi instance (#55930)
*`vsphere_copy` was only able to interact with a vCenter instance. This patch change that. * In addition, it also makes use of the `vmware_argument_spec`. Co-Authored-By: Abhijeet Kasurde <akasurde@redhat.com>
This commit is contained in:
parent
ca7ff2ad05
commit
216260cbb4
2 changed files with 99 additions and 57 deletions
4
changelogs/fragments/55930-vsphere_copy_for_esxi.yaml
Normal file
4
changelogs/fragments/55930-vsphere_copy_for_esxi.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
minor_changes:
|
||||
- vsphere_copy - The module can now also be used with standalone ESXi.
|
||||
- vsphere_copy - The ``host`` and ``login`` parameters are deprecated, use `hostname` and ``username``
|
||||
like for the other VMware modules.
|
|
@ -7,57 +7,61 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: vsphere_copy
|
||||
short_description: Copy a file to a vCenter datastore
|
||||
short_description: Copy a file to a VMware datastore
|
||||
description:
|
||||
- Upload files to a vCenter datastore
|
||||
- Upload files to a VMware datastore through a VCenter REST API.
|
||||
version_added: 2.0
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
options:
|
||||
hostname:
|
||||
version_added: "2.9"
|
||||
port:
|
||||
version_added: "2.9"
|
||||
username:
|
||||
version_added: "2.9"
|
||||
host:
|
||||
description:
|
||||
- The vCenter server on which the datastore is available.
|
||||
required: true
|
||||
- Use C(hostname) instead like the other VMware modules.
|
||||
- The vCenter or ESXi server on which the datastore is available.
|
||||
- This option is deprecated and will eventually be removed in 2.12.
|
||||
aliases: ['hostname']
|
||||
login:
|
||||
description:
|
||||
- The login name to authenticate on the vCenter server.
|
||||
required: true
|
||||
- Use C(username) instead like the other VMware modules.
|
||||
- The login name to authenticate on the vCenter or ESXi server.
|
||||
- This option is deprecated and will eventually be removed in 2.12.
|
||||
aliases: ['username']
|
||||
password:
|
||||
description:
|
||||
- The password to authenticate on the vCenter server.
|
||||
required: true
|
||||
src:
|
||||
description:
|
||||
- The file to push to vCenter
|
||||
- The file to push to vCenter.
|
||||
required: true
|
||||
type: str
|
||||
datacenter:
|
||||
description:
|
||||
- The datacenter on the vCenter server that holds the datastore.
|
||||
required: true
|
||||
required: false
|
||||
type: str
|
||||
datastore:
|
||||
description:
|
||||
- The datastore on the vCenter server to push files to.
|
||||
- The datastore to push files to.
|
||||
required: true
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- The file to push to the datastore on the vCenter server.
|
||||
- The file to push to the datastore.
|
||||
required: true
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be
|
||||
set to C(no) when no other option exists.
|
||||
default: 'yes'
|
||||
type: bool
|
||||
type: str
|
||||
timeout:
|
||||
description:
|
||||
- The timeout in seconds for the upload to the datastore.
|
||||
|
@ -66,26 +70,40 @@ options:
|
|||
version_added: "2.8"
|
||||
|
||||
notes:
|
||||
- "This module ought to be run from a system that can access vCenter directly and has the file to transfer.
|
||||
- "This module ought to be run from a system that can access the vCenter or the ESXi directly and has the file to transfer.
|
||||
It can be the normal remote target or you can change it either by using C(transport: local) or using C(delegate_to)."
|
||||
- Tested on vSphere 5.5
|
||||
- Tested on vSphere 5.5 and ESXi 6.7
|
||||
extends_documentation_fragment: vmware.documentation
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- vsphere_copy:
|
||||
host: '{{ vhost }}'
|
||||
login: '{{ vuser }}'
|
||||
password: '{{ vpass }}'
|
||||
- name: Copy file to datastore using delegate_to
|
||||
vsphere_copy:
|
||||
hostname: '{{ vcenter_hostname }}'
|
||||
username: '{{ vcenter_username }}'
|
||||
password: '{{ vcenter_password }}'
|
||||
src: /some/local/file
|
||||
datacenter: DC1 Someplace
|
||||
datastore: datastore1
|
||||
path: some/remote/file
|
||||
delegate_to: localhost
|
||||
|
||||
- vsphere_copy:
|
||||
host: '{{ vhost }}'
|
||||
login: '{{ vuser }}'
|
||||
password: '{{ vpass }}'
|
||||
- name: Copy file to datastore when datacenter is inside folder called devel
|
||||
vsphere_copy:
|
||||
hostname: '{{ vcenter_hostname }}'
|
||||
username: '{{ vcenter_username }}'
|
||||
password: '{{ vcenter_password }}'
|
||||
src: /some/local/file
|
||||
datacenter: devel/DC1
|
||||
datastore: datastore1
|
||||
path: some/remote/file
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Copy file to datastore using other_system
|
||||
vsphere_copy:
|
||||
hostname: '{{ vcenter_hostname }}'
|
||||
username: '{{ vcenter_username }}'
|
||||
password: '{{ vcenter_password }}'
|
||||
src: /other/local/file
|
||||
datacenter: DC2 Someplace
|
||||
datastore: datastore2
|
||||
|
@ -96,6 +114,7 @@ EXAMPLES = '''
|
|||
import atexit
|
||||
import errno
|
||||
import mmap
|
||||
import os
|
||||
import socket
|
||||
import traceback
|
||||
|
||||
|
@ -103,6 +122,7 @@ from ansible.module_utils.basic import AnsibleModule
|
|||
from ansible.module_utils.six.moves.urllib.parse import urlencode, quote
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.vmware import vmware_argument_spec
|
||||
|
||||
|
||||
def vmware_path(datastore, datacenter, path):
|
||||
|
@ -110,36 +130,41 @@ def vmware_path(datastore, datacenter, path):
|
|||
path = "/folder/%s" % quote(path.lstrip("/"))
|
||||
# Due to a software bug in vSphere, it fails to handle ampersand in datacenter names
|
||||
# The solution is to do what vSphere does (when browsing) and double-encode ampersands, maybe others ?
|
||||
datacenter = datacenter.replace('&', '%26')
|
||||
if not path.startswith("/"):
|
||||
path = "/" + path
|
||||
params = dict(dsName=datastore)
|
||||
if datacenter:
|
||||
datacenter = datacenter.replace('&', '%26')
|
||||
params["dcPath"] = datacenter
|
||||
params = urlencode(params)
|
||||
return "%s?%s" % (path, params)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = vmware_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
host=dict(required=False, removedin_version='2.12'),
|
||||
login=dict(required=False, removedin_version='2.12'),
|
||||
src=dict(required=True, aliases=['name']),
|
||||
datacenter=dict(required=False),
|
||||
datastore=dict(required=True),
|
||||
dest=dict(required=True, aliases=['path']),
|
||||
timeout=dict(default=10, type='int'))
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
host=dict(required=True, aliases=['hostname']),
|
||||
login=dict(required=True, aliases=['username']),
|
||||
password=dict(required=True, no_log=True),
|
||||
src=dict(required=True, aliases=['name']),
|
||||
datacenter=dict(required=True),
|
||||
datastore=dict(required=True),
|
||||
dest=dict(required=True, aliases=['path']),
|
||||
validate_certs=dict(default=True, type='bool'),
|
||||
timeout=dict(default=10, type='int')
|
||||
),
|
||||
argument_spec=argument_spec,
|
||||
# Implementing check-mode using HEAD is impossible, since size/date is not 100% reliable
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
host = module.params.get('host')
|
||||
login = module.params.get('login')
|
||||
if module.params['host'] is not None:
|
||||
module.deprecate("The 'host' option is being replaced by 'hostname'", version='2.12')
|
||||
if module.params['login'] is not None:
|
||||
module.deprecate("The 'login' option is being replaced by 'username'", version='2.12')
|
||||
|
||||
hostname = module.params['hostname'] or module.params['host']
|
||||
username = module.params['username'] or module.params['login']
|
||||
password = module.params.get('password')
|
||||
src = module.params.get('src')
|
||||
datacenter = module.params.get('datacenter')
|
||||
|
@ -148,14 +173,23 @@ def main():
|
|||
validate_certs = module.params.get('validate_certs')
|
||||
timeout = module.params.get('timeout')
|
||||
|
||||
fd = open(src, "rb")
|
||||
atexit.register(fd.close)
|
||||
try:
|
||||
fd = open(src, "rb")
|
||||
atexit.register(fd.close)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Failed to open src file %s" % to_native(e))
|
||||
|
||||
data = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
atexit.register(data.close)
|
||||
if os.stat(src).st_size == 0:
|
||||
data = ''
|
||||
else:
|
||||
data = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
atexit.register(data.close)
|
||||
|
||||
remote_path = vmware_path(datastore, datacenter, dest)
|
||||
url = 'https://%s%s' % (host, remote_path)
|
||||
|
||||
if not all([hostname, username, password]):
|
||||
module.fail_json(msg="One of following parameter is missing - hostname, username, password")
|
||||
url = 'https://%s%s' % (hostname, remote_path)
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/octet-stream",
|
||||
|
@ -164,12 +198,16 @@ def main():
|
|||
|
||||
try:
|
||||
r = open_url(url, data=data, headers=headers, method='PUT', timeout=timeout,
|
||||
url_username=login, url_password=password, validate_certs=validate_certs,
|
||||
url_username=username, url_password=password, validate_certs=validate_certs,
|
||||
force_basic_auth=True)
|
||||
except socket.error as e:
|
||||
if isinstance(e.args, tuple) and e[0] == errno.ECONNRESET:
|
||||
# VSphere resets connection if the file is in use and cannot be replaced
|
||||
module.fail_json(msg='Failed to upload, image probably in use', status=None, errno=e[0], reason=to_native(e), url=url)
|
||||
if isinstance(e.args, tuple):
|
||||
if len(e.args) > 0:
|
||||
if e[0] == errno.ECONNRESET:
|
||||
# VSphere resets connection if the file is in use and cannot be replaced
|
||||
module.fail_json(msg='Failed to upload, image probably in use', status=None, errno=e[0], reason=to_native(e), url=url)
|
||||
else:
|
||||
module.fail_json(msg=to_native(e))
|
||||
else:
|
||||
module.fail_json(msg=str(e), status=None, errno=e[0], reason=str(e),
|
||||
url=url, exception=traceback.format_exc())
|
||||
|
@ -178,7 +216,7 @@ def main():
|
|||
try:
|
||||
if isinstance(e[0], int):
|
||||
error_code = e[0]
|
||||
except KeyError:
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
module.fail_json(msg=to_native(e), status=None, errno=error_code,
|
||||
reason=to_native(e), url=url, exception=traceback.format_exc())
|
||||
|
|
Loading…
Reference in a new issue