1
0
Fork 0
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:
Matt Martz 2018-05-31 11:43:00 -05:00 committed by GitHub
parent 0fd7d7500a
commit 961484e00d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 137 additions and 3 deletions

View file

@ -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']

View 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

View file

@ -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