d0cf9617a5
Due to a spurious newline we corrupted the payload. It depends on the order of the headers and if there were headers added by vSphere. The Accept header was also not needed.
151 lines
5.1 KiB
Python
151 lines
5.1 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2015 Dag Wieers <dag@wieers.com>
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: vsphere_copy
|
|
short_description: Copy a file to a vCenter datastore
|
|
description: Upload files to a vCenter datastore
|
|
version_added: 2.0
|
|
author: Dag Wieers (@dagwieers) <dag@wieers.com>
|
|
options:
|
|
host:
|
|
description:
|
|
- The vCenter server on which the datastore is available.
|
|
required: true
|
|
login:
|
|
description:
|
|
- The login name to authenticate on the vCenter server.
|
|
required: true
|
|
password:
|
|
description:
|
|
- The password to authenticate on the vCenter server.
|
|
required: true
|
|
src:
|
|
description:
|
|
- The file to push to vCenter
|
|
required: true
|
|
datacenter:
|
|
description:
|
|
- The datacenter on the vCenter server that holds the datastore.
|
|
required: true
|
|
datastore:
|
|
description:
|
|
- The datastore on the vCenter server to push files to.
|
|
required: true
|
|
path:
|
|
description:
|
|
- The file to push to the datastore on the vCenter server.
|
|
required: true
|
|
notes:
|
|
- This module ought to be run from a system that can access vCenter 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
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- vsphere_copy: host=vhost login=vuser password=vpass src=/some/local/file datacenter='DC1 Someplace' datastore=datastore1 path=some/remote/file
|
|
transport: local
|
|
- vsphere_copy: host=vhost login=vuser password=vpass src=/other/local/file datacenter='DC2 Someplace' datastore=datastore2 path=other/remote/file
|
|
delegate_to: other_system
|
|
'''
|
|
|
|
import atexit
|
|
import base64
|
|
import httplib
|
|
import urllib
|
|
import mmap
|
|
import errno
|
|
import socket
|
|
|
|
def vmware_path(datastore, datacenter, path):
|
|
''' Constructs a URL path that VSphere accepts reliably '''
|
|
path = "/folder/%s" % path.lstrip("/")
|
|
if not path.startswith("/"):
|
|
path = "/" + path
|
|
params = dict( dsName = datastore )
|
|
if datacenter:
|
|
params["dcPath"] = datacenter
|
|
params = urllib.urlencode(params)
|
|
return "%s?%s" % (path, params)
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
argument_spec = dict(
|
|
host = dict(required=True, aliases=[ 'hostname' ]),
|
|
login = dict(required=True, aliases=[ 'username' ]),
|
|
password = dict(required=True),
|
|
src = dict(required=True, aliases=[ 'name' ]),
|
|
datacenter = dict(required=True),
|
|
datastore = dict(required=True),
|
|
dest = dict(required=True, aliases=[ 'path' ]),
|
|
),
|
|
# 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')
|
|
password = module.params.get('password')
|
|
src = module.params.get('src')
|
|
datacenter = module.params.get('datacenter')
|
|
datastore = module.params.get('datastore')
|
|
dest = module.params.get('dest')
|
|
|
|
fd = open(src, "rb")
|
|
atexit.register(fd.close)
|
|
|
|
data = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)
|
|
atexit.register(data.close)
|
|
|
|
conn = httplib.HTTPSConnection(host)
|
|
atexit.register(conn.close)
|
|
|
|
remote_path = vmware_path(datastore, datacenter, dest)
|
|
auth = base64.encodestring('%s:%s' % (login, password)).rstrip()
|
|
headers = {
|
|
"Content-Type": "application/octet-stream",
|
|
"Content-Length": str(len(data)),
|
|
"Authorization": "Basic %s" % auth,
|
|
}
|
|
|
|
# URL is only used in JSON output (helps troubleshooting)
|
|
url = 'https://%s%s' % (host, remote_path)
|
|
|
|
try:
|
|
conn.request("PUT", remote_path, body=data, headers=headers)
|
|
except socket.error, 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=e[0], reason=str(e), url=url)
|
|
else:
|
|
module.fail_json(msg=str(e), status=e[0], reason=str(e), url=url)
|
|
|
|
resp = conn.getresponse()
|
|
|
|
if resp.status in range(200, 300):
|
|
module.exit_json(changed=True, status=resp.status, reason=resp.reason, url=url)
|
|
else:
|
|
module.fail_json(msg='Failed to upload', status=resp.status, reason=resp.reason, length=resp.length, version=resp.version, headers=resp.getheaders(), chunked=resp.chunked, url=url)
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
main()
|