Add src parameter for uri module that can be used in place of body. Supports binary files (#33689)
* First pass at a src parameter that can be used in place of body. Supports binary files * Add test for uri src body * Bump version_added to 2.6 * Close the open file handle * Add uri action plugin that handles src/remote_src * Document remote_src * Remove duplicate info about remote_src * Bump version_added to 2.7
This commit is contained in:
parent
0fd7d7500a
commit
961484e00d
3 changed files with 137 additions and 3 deletions
|
@ -133,6 +133,17 @@ options:
|
|||
client authentication. If I(client_cert) contains both the certificate
|
||||
and key, this option is not required.
|
||||
version_added: '2.4'
|
||||
src:
|
||||
description:
|
||||
- Path to file to be submitted to the remote server. Cannot be used with I(body).
|
||||
version_added: '2.7'
|
||||
remote_src:
|
||||
description:
|
||||
- If C(no), the module will search for src on originating/master machine, if C(yes) the
|
||||
module will use the C(src) path on the remote/target machine.
|
||||
type: bool
|
||||
default: 'no'
|
||||
version_added: '2.7'
|
||||
notes:
|
||||
- The dependency on httplib2 was removed in Ansible 2.1.
|
||||
- The module returns all the HTTP headers in lower-case.
|
||||
|
@ -206,6 +217,19 @@ EXAMPLES = r'''
|
|||
password: "{{ jenkins.password }}"
|
||||
force_basic_auth: yes
|
||||
status_code: 201
|
||||
|
||||
- name: POST from contents of local file
|
||||
uri:
|
||||
url: "https://httpbin.org/post"
|
||||
method: POST
|
||||
src: file.json
|
||||
|
||||
- name: POST from contents of remote file
|
||||
uri:
|
||||
url: "https://httpbin.org/post"
|
||||
method: POST
|
||||
src: /path/to/my/file.json
|
||||
remote_src: true
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
|
@ -367,6 +391,19 @@ def uri(module, url, dest, body, body_format, method, headers, socket_timeout):
|
|||
redirected = False
|
||||
redir_info = {}
|
||||
r = {}
|
||||
|
||||
src = module.params['src']
|
||||
if src:
|
||||
try:
|
||||
headers.update({
|
||||
'Content-Length': os.stat(src).st_size
|
||||
})
|
||||
data = open(src, 'rb')
|
||||
except OSError:
|
||||
module.fail_json(msg='Unable to open source file %s' % src, exception=traceback.format_exc())
|
||||
else:
|
||||
data = body
|
||||
|
||||
if dest is not None:
|
||||
# Stash follow_redirects, in this block we don't want to follow
|
||||
# we'll reset back to the supplied value soon
|
||||
|
@ -393,7 +430,7 @@ def uri(module, url, dest, body, body_format, method, headers, socket_timeout):
|
|||
# Reset follow_redirects back to the stashed value
|
||||
module.params['follow_redirects'] = follow_redirects
|
||||
|
||||
resp, info = fetch_url(module, url, data=body, headers=headers,
|
||||
resp, info = fetch_url(module, url, data=data, headers=headers,
|
||||
method=method, timeout=socket_timeout)
|
||||
|
||||
try:
|
||||
|
@ -403,6 +440,13 @@ def uri(module, url, dest, body, body_format, method, headers, socket_timeout):
|
|||
# may have been stored in the info as 'body'
|
||||
content = info.pop('body', '')
|
||||
|
||||
if src:
|
||||
# Try to close the open file handle
|
||||
try:
|
||||
data.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
r['redirected'] = redirected or info['url'] != url
|
||||
r.update(redir_info)
|
||||
r.update(info)
|
||||
|
@ -418,6 +462,7 @@ def main():
|
|||
url_password=dict(type='str', aliases=['password'], no_log=True),
|
||||
body=dict(type='raw'),
|
||||
body_format=dict(type='str', default='raw', choices=['form-urlencoded', 'json', 'raw']),
|
||||
src=dict(type='path'),
|
||||
method=dict(type='str', default='GET', choices=['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'PATCH', 'TRACE', 'CONNECT', 'REFRESH']),
|
||||
return_content=dict(type='bool', default=False),
|
||||
follow_redirects=dict(type='str', default='safe', choices=['all', 'no', 'none', 'safe', 'urllib2', 'yes']),
|
||||
|
@ -432,7 +477,8 @@ def main():
|
|||
argument_spec=argument_spec,
|
||||
# TODO: Remove check_invalid_arguments in 2.9
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True
|
||||
add_file_common_args=True,
|
||||
mutually_exclusive=[['body', 'src']],
|
||||
)
|
||||
|
||||
url = module.params['url']
|
||||
|
|
59
lib/ansible/plugins/action/uri.py
Normal file
59
lib/ansible/plugins/action/uri.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# (c) 2015, Brian Coca <briancoca+dev@gmail.com>
|
||||
# (c) 2018, Matt Martz <matt@sivel.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.plugins.action import ActionBase
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
|
||||
TRANSFERS_FILES = True
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
if task_vars is None:
|
||||
task_vars = dict()
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
del tmp # tmp no longer has any effect
|
||||
|
||||
src = self._task.args.get('src', None)
|
||||
remote_src = boolean(self._task.args.get('remote_src', 'no'), strict=False)
|
||||
|
||||
try:
|
||||
if (src and remote_src) or not src:
|
||||
# everything is remote, so we just execute the module
|
||||
# without changing any of the module arguments
|
||||
raise _AnsibleActionDone(result=self._execute_module(task_vars=task_vars))
|
||||
|
||||
try:
|
||||
src = self._find_needle('files', src)
|
||||
except AnsibleError as e:
|
||||
raise AnsibleActionFail(to_native(e))
|
||||
|
||||
tmp_src = self._connection._shell.join_path(self._connection._shell.tmpdir, os.path.basename(src))
|
||||
self._transfer_file(src, tmp_src)
|
||||
self._fixup_perms2((self._connection._shell.tmpdir, tmp_src))
|
||||
|
||||
new_module_args = self._task.args.copy()
|
||||
new_module_args.update(
|
||||
dict(
|
||||
src=tmp_src,
|
||||
)
|
||||
)
|
||||
|
||||
result.update(self._execute_module('uri', module_args=new_module_args, task_vars=task_vars))
|
||||
except AnsibleAction as e:
|
||||
result.update(e.result)
|
||||
finally:
|
||||
self._remove_tmp_path(self._connection._shell.tmpdir)
|
||||
return result
|
|
@ -456,6 +456,35 @@
|
|||
environment:
|
||||
NETRC: "{{ output_dir|expanduser }}/netrc"
|
||||
|
||||
- name: Test JSON POST with src
|
||||
uri:
|
||||
url: "https://{{ httpbin_host}}/post"
|
||||
src: pass0.json
|
||||
method: POST
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: result
|
||||
|
||||
- name: Validate POST with src works
|
||||
assert:
|
||||
that:
|
||||
- result.json.json[0] == 'JSON Test Pattern pass1'
|
||||
|
||||
- name: Test JSON POST with src and remote_src=True
|
||||
uri:
|
||||
url: "https://{{ httpbin_host}}/post"
|
||||
src: "{{ role_path }}/files/pass0.json"
|
||||
remote_src: true
|
||||
method: POST
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: result
|
||||
|
||||
- name: Validate POST with src and remote_src=True works
|
||||
assert:
|
||||
that:
|
||||
- result.json.json[0] == 'JSON Test Pattern pass1'
|
||||
|
||||
- name: Test follow_redirects=none
|
||||
include_tasks: redirect-none.yml
|
||||
|
||||
|
@ -466,4 +495,4 @@
|
|||
include_tasks: redirect-urllib2.yml
|
||||
|
||||
- name: Test follow_redirects=all
|
||||
include_tasks: redirect-all.yml
|
||||
include_tasks: redirect-all.yml
|
Loading…
Reference in a new issue