#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2015 Dag Wieers # # 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 . 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) 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 #<> main()