1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Tweaking daisychain internals to allow get_url to modify the path destination when downloading to a directory.

Minor module refactoring.
This commit is contained in:
Michael DeHaan 2012-07-22 11:08:16 -04:00
parent 46650cfcec
commit 0b891fc8fb
4 changed files with 90 additions and 146 deletions

View file

@ -12,5 +12,5 @@
- jquery.min.js - jquery.min.js
- mobile/latest/jquery.mobile.min.js - mobile/latest/jquery.mobile.min.js
- ui/jquery-ui-git.css - ui/jquery-ui-git.css
- name: Pass urlencoded name to CGI #- name: Pass urlencoded name to CGI
action: get_url url=http://example.com/name.cgi?name='${person}' dest=/tmp/test # action: get_url url=http://example.com/name.cgi?name='${person}' dest=/tmp/test

View file

@ -128,22 +128,27 @@ class AnsibleModule(object):
log_args = re.sub(r'password=.+ (.*)', r"password=NOT_LOGGING_PASSWORD \1", self.args) log_args = re.sub(r'password=.+ (.*)', r"password=NOT_LOGGING_PASSWORD \1", self.args)
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % log_args) syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % log_args)
def jsonify(self, data):
return json.dumps(data)
def exit_json(self, **kwargs): def exit_json(self, **kwargs):
''' return from the module, without error ''' ''' return from the module, without error '''
print json.dumps(kwargs) print self.jsonify(kwargs)
sys.exit(0) sys.exit(0)
def fail_json(self, **kwargs): def fail_json(self, **kwargs):
''' return from the module, with an error message ''' ''' return from the module, with an error message '''
assert 'msg' in kwargs, "implementation error -- msg to explain the error is required" assert 'msg' in kwargs, "implementation error -- msg to explain the error is required"
kwargs['failed'] = True kwargs['failed'] = True
print json.dumps(kwargs) print self.jsonify(kwargs)
sys.exit(1) sys.exit(1)
def md5(self, filename): def md5(self, filename):
''' Return MD5 hex digest of local file, or None if file is not present. ''' ''' Return MD5 hex digest of local file, or None if file is not present. '''
if not os.path.exists(filename): if not os.path.exists(filename):
return None return None
if os.path.isdir(filename):
self.fail_json(msg="attempted to take md5sum of directory: %s" % filename)
digest = _md5() digest = _md5()
blocksize = 64 * 1024 blocksize = 64 * 1024
infile = open(filename, 'rb') infile = open(filename, 'rb')

View file

@ -537,6 +537,14 @@ class Runner(object):
group_hosts[g.name] = [ h.name for h in g.hosts ] group_hosts[g.name] = [ h.name for h in g.hosts ]
inject['groups'] = group_hosts inject['groups'] = group_hosts
# allow module args to work as a dictionary
# though it is usually a string
new_args = ""
if type(self.module_args) == dict:
for (k,v) in self.module_args.iteritems():
new_args = new_args + "%s='%s' " % (k,v)
self.module_args = new_args
conditional = utils.template(self.conditional, inject) conditional = utils.template(self.conditional, inject)
if not eval(conditional): if not eval(conditional):
result = utils.jsonify(dict(skipped=True)) result = utils.jsonify(dict(skipped=True))
@ -568,6 +576,8 @@ class Runner(object):
if result.is_successful() and 'daisychain' in result.result: if result.is_successful() and 'daisychain' in result.result:
chained = True chained = True
self.module_name = result.result['daisychain'] self.module_name = result.result['daisychain']
if 'daisychain_args' in result.result:
self.module_args = result.result['daisychain_args']
result2 = self._executor_internal_inner(host, inject, port) result2 = self._executor_internal_inner(host, inject, port)
changed = result.result.get('changed',False) or result2.result.get('changed',False) changed = result.result.get('changed',False) or result2.result.get('changed',False)
result.result.update(result2.result) result.result.update(result2.result)

View file

@ -17,169 +17,118 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# #
# Synopsis: # see examples/playbooks/get_url.yml
# ansible -m get_url -a "url=http://some.place/some.file dest=/tmp/file"
#
# Arguments:
# url= (mandatory, no default)
# dest= (mandatory, no default)
# if dest= is a file, url is copied to that file
# if dest= is a directory, determine name from url= and store it in dest/
# mode, owner, group, ... from the "file" module are also supported
#
# Playbook:
# The dest=<directory> feature lets you do this in a Playbook:
#
# - name: Grab a bunch of jQuery stuff
# action: get_url url=http://code.jquery.com/$item dest=${jquery_directory} mode=0444
# with_items:
# - jquery.min.js
# - mobile/latest/jquery.mobile.min.js
# - ui/jquery-ui-git.css
#
# TODO:
# timeout=
# Support gzip compression?
# http://www.diveintopython.net/http_web_services/gzip_compression.html
import sys
import os import os
import shlex
import shutil import shutil
import syslog import syslog
import datetime import datetime
import tempfile import tempfile
try:
from hashlib import md5 as _md5
except ImportError:
from md5 import md5 as _md5
HAS_URLLIB2=True HAS_URLLIB2=True
try: try:
import urllib2 import urllib2
except ImportError: except ImportError:
HAS_URLLIB2=False HAS_URLLIB2=False
HAS_URLPARSE=True HAS_URLPARSE=True
try: try:
import urlparse import urlparse
import socket import socket
except ImportError: except ImportError:
HAS_URLPARSE=False HAS_URLPARSE=False
# ==============================================================
# support
def md5(filename):
''' Return MD5 hex digest of local file, or None if file is not present. '''
if not os.path.exists(filename):
return None
digest = _md5()
blocksize = 64 * 1024
infile = open(filename, 'rb')
block = infile.read(blocksize)
while block:
digest.update(block)
block = infile.read(blocksize)
infile.close()
return digest.hexdigest()
# ============================================================== # ==============================================================
# url handling # url handling
def url_filename(url): def url_filename(url):
return os.path.basename(urlparse.urlsplit(url)[2]) fn = os.path.basename(urlparse.urlsplit(url)[2])
if fn == '':
return 'index.html'
return fn
def url_do_get(url, dest): def url_do_get(module, url, dest):
"""Get url and return request and info
Credits: http://stackoverflow.com/questions/7006574/how-to-download-file-from-ftp
""" """
Get url and return request and info
Credits: http://stackoverflow.com/questions/7006574/how-to-download-file-from-ftp
"""
USERAGENT = 'ansible-httpget' USERAGENT = 'ansible-httpget'
info = {} info = dict(url=url)
info['url'] = url
r = None r = None
actualdest = None
if dest: if os.path.isdir(dest):
if os.path.isdir(dest): urlfilename = url_filename(url)
destpath = "%s/%s" % (dest, url_filename(url)) actualdest = "%s/%s" % (dest, url_filename(url))
else: module.params['path'] = actualdest
destpath = dest
else: else:
destpath = url_filename(url) actualdest = dest
info['daisychain_args'] = module.params
info['destpath'] = destpath info['actualdest'] = actualdest
request = urllib2.Request(url) request = urllib2.Request(url)
request.add_header('User-agent', USERAGENT) request.add_header('User-agent', USERAGENT)
if os.path.exists(destpath): if os.path.exists(actualdest):
t = datetime.datetime.utcfromtimestamp(os.path.getmtime(destpath)) t = datetime.datetime.utcfromtimestamp(os.path.getmtime(actualdest))
tstamp = t.strftime('%a, %d %b %Y %H:%M:%S +0000') tstamp = t.strftime('%a, %d %b %Y %H:%M:%S +0000')
request.add_header('If-Modified-Since', tstamp) request.add_header('If-Modified-Since', tstamp)
try: try:
r = urllib2.urlopen(request) r = urllib2.urlopen(request)
info.update(r.info())
dinfo = dict(r.info()) info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 'unknown'), status=200))
for x in dinfo:
info[x] = dinfo[x]
info['msg'] = "OK %s octets" % r.headers.get('Content-Length', 'unknown')
info['status'] = 200
except urllib2.HTTPError as e: except urllib2.HTTPError as e:
# Must not fail_json() here so caller can handle HTTP 304 unmodified # Must not fail_json() here so caller can handle HTTP 304 unmodified
info['msg'] = "%s" % e info.update(dict(msg=str(e), status=e.code))
info['status'] = e.code
return r, info return r, info
except urllib2.URLError as e: except urllib2.URLError as e:
if 'code' in e: code = getattr(e, 'code', -1)
co = e.code module.fail_json(msg="Request failed: %s" % str(e), status_code=code)
else:
co = -1
resp = "%s" % e
module.fail_json(msg="Request failed", status_code=co, response=resp)
return r, info return r, info
def url_get(url, dest): def url_get(module, url, dest):
"""Get url and store at dest. If dest is a directory, determine filename """
from url, otherwise dest is a file Download url and store at dest.
Return info about the request. If dest is a directory, determine filename from url.
Return (tempfile, info about the request)
""" """
req, info = url_do_get(url, dest) req, info = url_do_get(module, url, dest)
# TODO: should really handle 304, but how? src file could exist (and be
# newer) but be empty ...
# TODO: should really handle 304, but how? src file could exist (and be newer) but empty
if info['status'] == 304: if info['status'] == 304:
module.exit_json(url=url, dest=info.get('destpath', dest), changed=False, msg=info.get('msg', '')) module.exit_json(url=url, dest=info.get('actualdest', dest), changed=False, msg=info.get('msg', ''))
# We have the data. Create a temporary file and copy content into that # create a temporary file and copy content to do md5-based replacement
# to do the MD5-thing if info['status'] != 200:
if info['status'] == 200:
destpath = info['destpath']
fd, tempname = tempfile.mkstemp()
f = os.fdopen(fd, 'wb')
try:
shutil.copyfileobj(req, f)
except Exception, err:
os.remove(tempname)
module.fail_json(msg="failed to create temporary content file: %s" % str(err))
f.close()
req.close()
return tempname, info
else:
module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url) module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url)
actualdest = info['actualdest']
fd, tempname = tempfile.mkstemp()
f = os.fdopen(fd, 'wb')
try:
shutil.copyfileobj(req, f)
except Exception, err:
os.remove(tempname)
module.fail_json(msg="failed to create temporary content file: %s" % str(err))
f.close()
req.close()
return tempname, info
# ============================================================== # ==============================================================
# main # main
def main(): def main():
global module
# does this really happen on non-ancient python?
if not HAS_URLLIB2:
module.fail_json(msg="urllib2 is not installed")
if not HAS_URLPARSE:
module.fail_json(msg="urlparse is not installed")
module = AnsibleModule( module = AnsibleModule(
argument_spec = dict( argument_spec = dict(
url = dict(required=True), url = dict(required=True),
@ -187,31 +136,14 @@ def main():
) )
) )
url = module.params.get('url', '') url = module.params['url']
dest = module.params.get('dest', '') dest = os.path.expanduser(module.params['dest'])
if url == "": # download to tmpsrc
module.fail_json(msg="url= URL missing") tmpsrc, info = url_get(module, url, dest)
if dest == "": md5sum_src = None
module.fail_json(msg="dest= missing") md5sum_dest = None
dest = info['actualdest']
dest = os.path.expanduser(dest)
if not HAS_URLLIB2:
module.fail_json(msg="urllib2 is not installed")
if not HAS_URLPARSE:
module.fail_json(msg="urlparse is not installed")
# Here we go... if this succeeds, tmpsrc is the name of a temporary file
# containing slurped content. If it fails, we've already raised an error
# to Ansible
tmpsrc, info = url_get(url, dest)
md5sum_src = None
dest = info.get('destpath', None)
# raise an error if there is no tmpsrc file # raise an error if there is no tmpsrc file
if not os.path.exists(tmpsrc): if not os.path.exists(tmpsrc):
@ -220,10 +152,8 @@ def main():
if not os.access(tmpsrc, os.R_OK): if not os.access(tmpsrc, os.R_OK):
os.remove(tmpsrc) os.remove(tmpsrc)
module.fail_json( msg="Source %s not readable" % (tmpsrc)) module.fail_json( msg="Source %s not readable" % (tmpsrc))
md5sum_src = md5(tmpsrc) md5sum_src = module.md5(tmpsrc)
md5sum_dest = None
# check if there is no dest file # check if there is no dest file
if os.path.exists(dest): if os.path.exists(dest):
# raise an error if copy has no permission on dest # raise an error if copy has no permission on dest
@ -233,14 +163,13 @@ def main():
if not os.access(dest, os.R_OK): if not os.access(dest, os.R_OK):
os.remove(tmpsrc) os.remove(tmpsrc)
module.fail_json( msg="Destination %s not readable" % (dest)) module.fail_json( msg="Destination %s not readable" % (dest))
md5sum_dest = md5(dest) md5sum_dest = module.md5(dest)
else: else:
if not os.access(os.path.dirname(dest), os.W_OK): if not os.access(os.path.dirname(dest), os.W_OK):
os.remove(tmpsrc) os.remove(tmpsrc)
module.fail_json( msg="Destination %s not writable" % (os.path.dirname(dest))) module.fail_json( msg="Destination %s not writable" % (os.path.dirname(dest)))
if md5sum_src != md5sum_dest: if md5sum_src != md5sum_dest:
# was os.system("cp %s %s" % (src, dest))
try: try:
shutil.copyfile(tmpsrc, dest) shutil.copyfile(tmpsrc, dest)
except Exception, err: except Exception, err:
@ -250,12 +179,12 @@ def main():
else: else:
changed = False changed = False
# Mission complete
os.remove(tmpsrc) os.remove(tmpsrc)
module.exit_json(url=url, dest=dest, src=tmpsrc,
md5sum=md5sum_src, changed=changed, msg=info.get('msg', ''), # Mission complete
daisychain="file") module.exit_json(url=url, dest=dest, src=tmpsrc, md5sum=md5sum_src,
changed=changed, msg=info.get('msg',''),
daisychain="file", daisychain_args=info.get('daisychain_args',''))
# this is magic, see lib/ansible/module_common.py # this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>> #<<INCLUDE_ANSIBLE_MODULE_COMMON>>