mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
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
|
client authentication. If I(client_cert) contains both the certificate
|
||||||
and key, this option is not required.
|
and key, this option is not required.
|
||||||
version_added: '2.4'
|
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:
|
notes:
|
||||||
- The dependency on httplib2 was removed in Ansible 2.1.
|
- The dependency on httplib2 was removed in Ansible 2.1.
|
||||||
- The module returns all the HTTP headers in lower-case.
|
- The module returns all the HTTP headers in lower-case.
|
||||||
|
@ -206,6 +217,19 @@ EXAMPLES = r'''
|
||||||
password: "{{ jenkins.password }}"
|
password: "{{ jenkins.password }}"
|
||||||
force_basic_auth: yes
|
force_basic_auth: yes
|
||||||
status_code: 201
|
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'''
|
RETURN = r'''
|
||||||
|
@ -367,6 +391,19 @@ def uri(module, url, dest, body, body_format, method, headers, socket_timeout):
|
||||||
redirected = False
|
redirected = False
|
||||||
redir_info = {}
|
redir_info = {}
|
||||||
r = {}
|
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:
|
if dest is not None:
|
||||||
# Stash follow_redirects, in this block we don't want to follow
|
# Stash follow_redirects, in this block we don't want to follow
|
||||||
# we'll reset back to the supplied value soon
|
# 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
|
# Reset follow_redirects back to the stashed value
|
||||||
module.params['follow_redirects'] = follow_redirects
|
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)
|
method=method, timeout=socket_timeout)
|
||||||
|
|
||||||
try:
|
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'
|
# may have been stored in the info as 'body'
|
||||||
content = info.pop('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['redirected'] = redirected or info['url'] != url
|
||||||
r.update(redir_info)
|
r.update(redir_info)
|
||||||
r.update(info)
|
r.update(info)
|
||||||
|
@ -418,6 +462,7 @@ def main():
|
||||||
url_password=dict(type='str', aliases=['password'], no_log=True),
|
url_password=dict(type='str', aliases=['password'], no_log=True),
|
||||||
body=dict(type='raw'),
|
body=dict(type='raw'),
|
||||||
body_format=dict(type='str', default='raw', choices=['form-urlencoded', 'json', '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']),
|
method=dict(type='str', default='GET', choices=['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'PATCH', 'TRACE', 'CONNECT', 'REFRESH']),
|
||||||
return_content=dict(type='bool', default=False),
|
return_content=dict(type='bool', default=False),
|
||||||
follow_redirects=dict(type='str', default='safe', choices=['all', 'no', 'none', 'safe', 'urllib2', 'yes']),
|
follow_redirects=dict(type='str', default='safe', choices=['all', 'no', 'none', 'safe', 'urllib2', 'yes']),
|
||||||
|
@ -432,7 +477,8 @@ def main():
|
||||||
argument_spec=argument_spec,
|
argument_spec=argument_spec,
|
||||||
# TODO: Remove check_invalid_arguments in 2.9
|
# TODO: Remove check_invalid_arguments in 2.9
|
||||||
check_invalid_arguments=False,
|
check_invalid_arguments=False,
|
||||||
add_file_common_args=True
|
add_file_common_args=True,
|
||||||
|
mutually_exclusive=[['body', 'src']],
|
||||||
)
|
)
|
||||||
|
|
||||||
url = module.params['url']
|
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:
|
environment:
|
||||||
NETRC: "{{ output_dir|expanduser }}/netrc"
|
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
|
- name: Test follow_redirects=none
|
||||||
include_tasks: redirect-none.yml
|
include_tasks: redirect-none.yml
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue