* allow shells to have per host options, remote_tmp

added language to shell
removed module lang setting from general as  plugins have it now
use get to avoid bad powershell plugin
more resilient tmp discovery, fall back to `pwd`
add shell to docs
fixed options for when frags are only options
added shell set ops in t_e and fixed option frags
normalize tmp dir usag4e

- pass tmpdir/tmp/temp options as env var to commands, making it default for tempfile
- adjusted ansiballz tmpdir
- default local tempfile usage to the configured local tmp
- set env temp in action

add options to powershell
shift temporary to internal envvar/params
ensure tempdir is set if we pass var
ensure basic and url use expected tempdir
ensure localhost uses local tmp
give /var/tmp priority, less perms issues
more consistent tempfile mgmt for ansiballz
made async_dir configurable
better action handling, allow for finally rm tmp
fixed tmp issue and no more tempdir in ballz
hostvarize world readable and admin users
always set shell tempdir
added comment to discourage use of exception/flow control

* Mostly revert expand_user as it's not quite working.

This was an additional feature anyhow.

Kept the use of pwd as a fallback but moved it to a second ssh
connection.  This is not optimal but getting that to work in a single
ssh connection was part of the problem holding this up.

(cherry picked from commit 395b714120522f15e4c90a346f5e8e8d79213aca)

* fixed script and other action plugins

ensure tmpdir deletion
allow for connections that don't support new options (legacy, 3rd party)
fixed tests
This commit is contained in:
Brian Coca
2018-01-16 00:15:04 -05:00
committed by Toshio Kuratomi
parent eca3fcd214
commit bbd6b8bb42
44 changed files with 1010 additions and 972 deletions

View File

@@ -1,19 +1,6 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
# Copyright: (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
# Copyright: (c) 2018, Ansible Project
# 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)
@@ -65,13 +52,14 @@ class ActionBase(with_metaclass(ABCMeta, object)):
self._loader = loader
self._templar = templar
self._shared_loader_obj = shared_loader_obj
# Backwards compat: self._display isn't really needed, just import the global display and use that.
self._display = display
self._cleanup_remote_tmp = False
self._supports_check_mode = True
self._supports_async = False
# Backwards compat: self._display isn't really needed, just import the global display and use that.
self._display = display
@abstractmethod
def run(self, tmp=None, task_vars=None):
""" Action Plugins should implement this method to perform their
@@ -99,6 +87,11 @@ class ActionBase(with_metaclass(ABCMeta, object)):
elif self._task.async_val and self._play_context.check_mode:
raise AnsibleActionFail('check mode and async cannot be used on same task.')
if not tmp and self._early_needs_tmp_path():
self._make_tmp_path()
else:
self._connection._shell.tempdir = tmp
return result
def _remote_file_exists(self, path):
@@ -236,16 +229,20 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if remote_user is None:
remote_user = self._play_context.remote_user
try:
admin_users = self._connection._shell.get_option('admin_users') + [remote_user]
except KeyError:
admin_users = ['root', remote_user] # plugin does not support admin_users
try:
remote_tmp = self._connection._shell.get_option('remote_temp')
except KeyError:
remote_tmp = '~/ansible'
# deal with tmpdir creation
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
use_system_tmp = False
if self._play_context.become and self._play_context.become_user not in ('root', remote_user):
use_system_tmp = True
tmp_mode = 0o700
tmpdir = self._remote_expand_user(self._play_context.remote_tmp_dir, sudoable=False)
cmd = self._connection._shell.mkdtemp(basefile, use_system_tmp, tmp_mode, tmpdir)
use_system_tmp = bool(self._play_context.become and self._play_context.become_user not in admin_users)
tmpdir = self._remote_expand_user(remote_tmp, sudoable=False)
cmd = self._connection._shell.mkdtemp(basefile=basefile, system=use_system_tmp, tmpdir=tmpdir)
result = self._low_level_execute_command(cmd, sudoable=False)
# error handling on this seems a little aggressive?
@@ -287,11 +284,14 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if rc == '/':
raise AnsibleError('failed to resolve remote temporary directory from %s: `%s` returned empty string' % (basefile, cmd))
self._connection._shell.tempdir = rc
if not use_system_tmp:
self._connection._shell.env.update({'ANSIBLE_REMOTE_TEMP': self._connection._shell.tempdir})
return rc
def _should_remove_tmp_path(self, tmp_path):
'''Determine if temporary path should be deleted or kept by user request/config'''
return tmp_path and self._cleanup_remote_tmp and not C.DEFAULT_KEEP_REMOTE_FILES and "-tmp-" in tmp_path
def _remove_tmp_path(self, tmp_path):
@@ -320,7 +320,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if isinstance(data, dict):
data = jsonify(data)
afd, afile = tempfile.mkstemp()
afd, afile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP)
afo = os.fdopen(afd, 'wb')
try:
data = to_bytes(data, errors='surrogate_or_strict')
@@ -393,7 +393,12 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# we have a need for it, at which point we'll have to do something different.
return remote_paths
if self._play_context.become and self._play_context.become_user and self._play_context.become_user not in ('root', remote_user):
try:
admin_users = self._connection._shell.get_option('admin_users')
except KeyError:
admin_users = ['root'] # plugin does not support admin users
if self._play_context.become and self._play_context.become_user and self._play_context.become_user not in admin_users + [remote_user]:
# Unprivileged user that's different than the ssh user. Let's get
# to work!
@@ -420,12 +425,12 @@ class ActionBase(with_metaclass(ABCMeta, object)):
raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], to_native(res['stderr'])))
res = self._remote_chown(remote_paths, self._play_context.become_user)
if res['rc'] != 0 and remote_user == 'root':
if res['rc'] != 0 and remote_user in admin_users:
# chown failed even if remove_user is root
raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as root. '
raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as a privileged user. '
'Unprivileged become user would be unable to read the file.')
elif res['rc'] != 0:
if C.ALLOW_WORLD_READABLE_TMPFILES:
if self._connection._shell('allow_world_readable_temp'):
# chown and fs acls failed -- do things this insecure
# way only if the user opted in in the config file
display.warning('Using world-readable permissions for temporary files Ansible needs to create when becoming an unprivileged user. '
@@ -534,33 +539,46 @@ class ActionBase(with_metaclass(ABCMeta, object)):
finally:
return x # pylint: disable=lost-exception
def _remote_expand_user(self, path, sudoable=True):
''' takes a remote path and performs tilde expansion on the remote host '''
if not path.startswith('~'): # FIXME: Windows paths may start with "~ instead of just ~
def _remote_expand_user(self, path, sudoable=True, pathsep=None):
''' takes a remote path and performs tilde/$HOME expansion on the remote host '''
# We only expand ~/path and ~username/path
if not path.startswith('~'):
return path
# FIXME: Can't use os.path.sep for Windows paths.
# Per Jborean, we don't have to worry about Windows as we don't have a notion of user's home
# dir there.
split_path = path.split(os.path.sep, 1)
expand_path = split_path[0]
if sudoable and expand_path == '~' and self._play_context.become and self._play_context.become_user:
expand_path = '~%s' % self._play_context.become_user
# use shell to construct appropriate command and execute
cmd = self._connection._shell.expand_user(expand_path)
data = self._low_level_execute_command(cmd, sudoable=False)
try:
initial_fragment = data['stdout'].strip().splitlines()[-1]
except IndexError:
initial_fragment = None
if not initial_fragment:
# Something went wrong trying to expand the path remotely. Return
# Something went wrong trying to expand the path remotely. Try using pwd, if not, return
# the original string
return path
cmd = self._connection._shell.pwd()
pwd = self._low_level_execute_command(cmd, sudoable=False).get('stdout', '').strip()
if pwd:
expanded = pwd
else:
expanded = path
if len(split_path) > 1:
return self._connection._shell.join_path(initial_fragment, *split_path[1:])
elif len(split_path) > 1:
expanded = self._connection._shell.join_path(initial_fragment, *split_path[1:])
else:
return initial_fragment
expanded = initial_fragment
return expanded
def _strip_success_message(self, data):
'''
@@ -655,8 +673,11 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if not self._is_pipelining_enabled(module_style, wrap_async):
# we might need remote tmp dir
if not tmp or 'tmp' not in tmp:
tmp = self._make_tmp_path()
if not tmp:
if not self._connection._shell.tempdir or tmp is None or 'tmp' not in tmp:
tmp = self._make_tmp_path()
else:
tmp = self._connection._shell.tempdir
remote_module_filename = self._connection._shell.get_remote_filename(module_path)
remote_module_path = self._connection._shell.join_path(tmp, remote_module_filename)
@@ -733,14 +754,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
else:
cmd = remote_module_path
rm_tmp = None
if self._should_remove_tmp_path(tmp) and not persist_files and delete_remote_tmp:
if not self._play_context.become or self._play_context.become_user == 'root':
# not sudoing or sudoing to root, so can cleanup files in the same step
rm_tmp = tmp
cmd = self._connection._shell.build_module_command(environment_string, shebang, cmd, arg_path=args_file_path, rm_tmp=rm_tmp).strip()
cmd = self._connection._shell.build_module_command(environment_string, shebang, cmd, arg_path=args_file_path).strip()
# Fix permissions of the tmp path and tmp files. This should be called after all files have been transferred.
if remote_files:
@@ -756,15 +770,12 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# NOTE: INTERNAL KEYS ONLY ACCESSIBLE HERE
# get internal info before cleaning
tmpdir_delete = (not data.pop("_ansible_suppress_tmpdir_delete", False) and wrap_async)
if data.pop("_ansible_suppress_tmpdir_delete", False):
self._cleanup_remote_tmp = False
# remove internal keys
remove_internal_keys(data)
# cleanup tmp?
if (self._play_context.become and self._play_context.become_user != 'root') and not persist_files and delete_remote_tmp or tmpdir_delete:
self._remove_tmp_path(tmp)
# FIXME: for backwards compat, figure out if still makes sense
if wrap_async:
data['changed'] = True

View File

@@ -25,7 +25,8 @@ import os.path
import re
import tempfile
from ansible.errors import AnsibleError
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleAction, AnsibleActionDone, AnsibleActionFail
from ansible.module_utils._text import to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
@@ -39,7 +40,7 @@ class ActionModule(ActionBase):
def _assemble_from_fragments(self, src_path, delimiter=None, compiled_regexp=None, ignore_hidden=False, decrypt=True):
''' assemble a file from a directory of fragments '''
tmpfd, temp_path = tempfile.mkstemp()
tmpfd, temp_path = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP)
tmp = os.fdopen(tmpfd, 'wb')
delimit_me = False
add_newline = False
@@ -96,78 +97,73 @@ class ActionModule(ActionBase):
ignore_hidden = self._task.args.get('ignore_hidden', False)
decrypt = self._task.args.get('decrypt', True)
if src is None or dest is None:
result['failed'] = True
result['msg'] = "src and dest are required"
return result
try:
if src is None or dest is None:
raise AnsibleActionFail("src and dest are required")
if boolean(remote_src, strict=False):
result.update(self._execute_module(tmp=tmp, task_vars=task_vars))
return result
else:
try:
src = self._find_needle('files', src)
except AnsibleError as e:
result['failed'] = True
result['msg'] = to_native(e)
return result
if boolean(remote_src, strict=False):
result.update(self._execute_module(tmp=tmp, task_vars=task_vars))
raise AnsibleActionDone()
else:
try:
src = self._find_needle('files', src)
except AnsibleError as e:
raise AnsibleActionFail(to_native(e))
if not tmp:
tmp = self._make_tmp_path()
if not os.path.isdir(src):
raise AnsibleActionFail(u"Source (%s) is not a directory" % src)
if not os.path.isdir(src):
result['failed'] = True
result['msg'] = u"Source (%s) is not a directory" % src
return result
_re = None
if regexp is not None:
_re = re.compile(regexp)
_re = None
if regexp is not None:
_re = re.compile(regexp)
# Does all work assembling the file
path = self._assemble_from_fragments(src, delimiter, _re, ignore_hidden, decrypt)
# Does all work assembling the file
path = self._assemble_from_fragments(src, delimiter, _re, ignore_hidden, decrypt)
path_checksum = checksum_s(path)
dest = self._remote_expand_user(dest)
dest_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=follow, tmp=tmp)
path_checksum = checksum_s(path)
dest = self._remote_expand_user(dest)
dest_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=follow, tmp=tmp)
diff = {}
diff = {}
# setup args for running modules
new_module_args = self._task.args.copy()
# setup args for running modules
new_module_args = self._task.args.copy()
# clean assemble specific options
for opt in ['remote_src', 'regexp', 'delimiter', 'ignore_hidden', 'decrypt']:
if opt in new_module_args:
del new_module_args[opt]
# clean assemble specific options
for opt in ['remote_src', 'regexp', 'delimiter', 'ignore_hidden', 'decrypt']:
if opt in new_module_args:
del new_module_args[opt]
new_module_args.update(
dict(
dest=dest,
original_basename=os.path.basename(src),
new_module_args.update(
dict(
dest=dest,
original_basename=os.path.basename(src),
)
)
)
if path_checksum != dest_stat['checksum']:
if path_checksum != dest_stat['checksum']:
if self._play_context.diff:
diff = self._get_diff_data(dest, path, task_vars)
if self._play_context.diff:
diff = self._get_diff_data(dest, path, task_vars)
remote_path = self._connection._shell.join_path(tmp, 'src')
xfered = self._transfer_file(path, remote_path)
remote_path = self._connection._shell.join_path(self._connection._shell.tempdir, 'src')
xfered = self._transfer_file(path, remote_path)
# fix file permissions when the copy is done as a different user
self._fixup_perms2((tmp, remote_path))
# fix file permissions when the copy is done as a different user
self._fixup_perms2((self._connection._shell.tempdir, remote_path))
new_module_args.update(dict(src=xfered,))
new_module_args.update(dict(src=xfered,))
res = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=False)
if diff:
res['diff'] = diff
result.update(res)
else:
result.update(self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=False))
res = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp)
if diff:
res['diff'] = diff
result.update(res)
else:
result.update(self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp))
self._remove_tmp_path(tmp)
except AnsibleAction as e:
result.update(e.result)
finally:
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -22,4 +22,8 @@ class ActionModule(ActionBase):
wrap_async = self._task.async_val and not self._connection.has_native_async
results = merge_hash(results, self._execute_module(tmp=tmp, task_vars=task_vars, wrap_async=wrap_async))
if not wrap_async:
# remove a temporary path we created
self._remove_tmp_path(self._connection._shell.tempdir)
return results

View File

@@ -26,8 +26,8 @@ import os.path
import stat
import tempfile
import traceback
from itertools import chain
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
@@ -186,12 +186,13 @@ def _walk_dirs(topdir, base_path=None, local_follow=False, trailing_slash_detect
class ActionModule(ActionBase):
TRANSFERS_FILES = True
def _create_remote_file_args(self, module_args):
# remove action plugin only keys
return dict((k, v) for k, v in module_args.items() if k not in ('content', 'decrypt'))
def _copy_file(self, source_full, source_rel, content, content_tempfile,
dest, task_vars, tmp, delete_remote_tmp):
def _copy_file(self, source_full, source_rel, content, content_tempfile, dest, task_vars, tmp):
decrypt = boolean(self._task.args.get('decrypt', True), strict=False)
follow = boolean(self._task.args.get('follow', False), strict=False)
force = boolean(self._task.args.get('force', 'yes'), strict=False)
@@ -206,7 +207,6 @@ class ActionModule(ActionBase):
except AnsibleFileNotFound as e:
result['failed'] = True
result['msg'] = "could not find src=%s, %s" % (source_full, to_text(e))
self._remove_tmp_path(tmp)
return result
# Get the local mode and set if user wanted it preserved
@@ -221,13 +221,7 @@ class ActionModule(ActionBase):
if self._connection._shell.path_has_trailing_slash(dest):
dest_file = self._connection._shell.join_path(dest, source_rel)
else:
dest_file = self._connection._shell.join_path(dest)
# Create a tmp path if missing only if this is not recursive.
# If this is recursive we already have a tmp path.
if delete_remote_tmp:
if tmp is None or "-tmp-" not in tmp:
tmp = self._make_tmp_path()
dest_file = dest
# Attempt to get remote file info
dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow, tmp=tmp, checksum=force)
@@ -237,7 +231,6 @@ class ActionModule(ActionBase):
if content is not None:
# If source was defined as content remove the temporary file and fail out.
self._remove_tempfile_if_content_defined(content, content_tempfile)
self._remove_tmp_path(tmp)
result['failed'] = True
result['msg'] = "can not use content with a dir as dest"
return result
@@ -265,7 +258,7 @@ class ActionModule(ActionBase):
return result
# Define a remote directory that we will copy the file to.
tmp_src = self._connection._shell.join_path(tmp, 'source')
tmp_src = self._connection._shell.join_path(self._connection._shell.tempdir, 'source')
remote_path = None
@@ -280,7 +273,7 @@ class ActionModule(ActionBase):
# fix file permissions when the copy is done as a different user
if remote_path:
self._fixup_perms2((tmp, remote_path))
self._fixup_perms2((self._connection._shell.tempdir, remote_path))
if raw:
# Continue to next iteration if raw is defined.
@@ -301,9 +294,7 @@ class ActionModule(ActionBase):
if lmode:
new_module_args['mode'] = lmode
module_return = self._execute_module(module_name='copy',
module_args=new_module_args, task_vars=task_vars,
tmp=tmp, delete_remote_tmp=delete_remote_tmp)
module_return = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp)
else:
# no need to transfer the file, already correct hash, but still need to call
@@ -312,8 +303,6 @@ class ActionModule(ActionBase):
self._loader.cleanup_tmp_file(source_full)
if raw:
# Continue to next iteration if raw is defined.
self._remove_tmp_path(tmp)
return None
# Fix for https://github.com/ansible/ansible-modules-core/issues/1568.
@@ -339,9 +328,7 @@ class ActionModule(ActionBase):
new_module_args['mode'] = lmode
# Execute the file module.
module_return = self._execute_module(module_name='file',
module_args=new_module_args, task_vars=task_vars,
tmp=tmp, delete_remote_tmp=delete_remote_tmp)
module_return = self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp)
if not module_return.get('checksum'):
module_return['checksum'] = local_checksum
@@ -379,7 +366,7 @@ class ActionModule(ActionBase):
def _create_content_tempfile(self, content):
''' Create a tempfile containing defined content '''
fd, content_tempfile = tempfile.mkstemp()
fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP)
f = os.fdopen(fd, 'wb')
content = to_bytes(content)
try:
@@ -402,6 +389,9 @@ class ActionModule(ActionBase):
result = super(ActionModule, self).run(tmp, task_vars)
if tmp is None:
tmp = self._connection._shell.tempdir
source = self._task.args.get('src', None)
content = self._task.args.get('content', None)
dest = self._task.args.get('dest', None)
@@ -493,19 +483,6 @@ class ActionModule(ActionBase):
# Used to cut down on command calls when not recursive.
module_executed = False
# Optimization: Can delete remote_tmp on the first call if we're only
# copying a single file. Otherwise we keep the remote_tmp until it
# is no longer needed.
delete_remote_tmp = False
if sum(len(f) for f in chain(source_files.values())) == 1:
# Tell _execute_module to delete the file if there is one file.
delete_remote_tmp = True
# If this is a recursive action create a tmp path that we can share as the _exec_module create is too late.
if not delete_remote_tmp:
if tmp is None or "-tmp-" not in tmp:
tmp = self._make_tmp_path()
# expand any user home dir specifier
dest = self._remote_expand_user(dest)
@@ -513,7 +490,7 @@ class ActionModule(ActionBase):
for source_full, source_rel in source_files['files']:
# copy files over. This happens first as directories that have
# a file do not need to be created later
module_return = self._copy_file(source_full, source_rel, content, content_tempfile, dest, task_vars, tmp, delete_remote_tmp)
module_return = self._copy_file(source_full, source_rel, content, content_tempfile, dest, task_vars, tmp)
if module_return is None:
continue
@@ -539,9 +516,7 @@ class ActionModule(ActionBase):
new_module_args['state'] = 'directory'
new_module_args['mode'] = self._task.args.get('directory_mode', None)
module_return = self._execute_module(module_name='file',
module_args=new_module_args, task_vars=task_vars,
tmp=tmp, delete_remote_tmp=delete_remote_tmp)
module_return = self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp)
module_executed = True
changed = changed or module_return.get('changed', False)
@@ -553,15 +528,11 @@ class ActionModule(ActionBase):
new_module_args['state'] = 'link'
new_module_args['force'] = True
module_return = self._execute_module(module_name='file',
module_args=new_module_args, task_vars=task_vars,
tmp=tmp, delete_remote_tmp=delete_remote_tmp)
module_return = self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp)
module_executed = True
if module_return.get('failed'):
result.update(module_return)
if not delete_remote_tmp:
self._remove_tmp_path(tmp)
return result
changed = changed or module_return.get('changed', False)
@@ -571,13 +542,12 @@ class ActionModule(ActionBase):
if 'path' in module_return and 'dest' not in module_return:
module_return['dest'] = module_return['path']
# Delete tmp path if we were recursive or if we did not execute a module.
if not delete_remote_tmp or (delete_remote_tmp and not module_executed):
self._remove_tmp_path(tmp)
if module_executed and len(source_files['files']) == 1:
result.update(module_return)
else:
result.update(dict(dest=dest, src=source, changed=changed))
# Delete tmp path
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -44,170 +44,174 @@ class ActionModule(ActionBase):
result = super(ActionModule, self).run(tmp, task_vars)
if self._play_context.check_mode:
result['skipped'] = True
result['msg'] = 'check mode not (yet) supported for this module'
return result
try:
if self._play_context.check_mode:
result['skipped'] = True
result['msg'] = 'check mode not (yet) supported for this module'
return result
source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
flat = boolean(self._task.args.get('flat'), strict=False)
fail_on_missing = boolean(self._task.args.get('fail_on_missing'), strict=False)
validate_checksum = boolean(self._task.args.get('validate_checksum',
self._task.args.get('validate_md5', True)),
strict=False)
source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
flat = boolean(self._task.args.get('flat'), strict=False)
fail_on_missing = boolean(self._task.args.get('fail_on_missing'), strict=False)
validate_checksum = boolean(self._task.args.get('validate_checksum',
self._task.args.get('validate_md5', True)),
strict=False)
# validate source and dest are strings FIXME: use basic.py and module specs
if not isinstance(source, string_types):
result['msg'] = "Invalid type supplied for source option, it must be a string"
# validate source and dest are strings FIXME: use basic.py and module specs
if not isinstance(source, string_types):
result['msg'] = "Invalid type supplied for source option, it must be a string"
if not isinstance(dest, string_types):
result['msg'] = "Invalid type supplied for dest option, it must be a string"
if not isinstance(dest, string_types):
result['msg'] = "Invalid type supplied for dest option, it must be a string"
# validate_md5 is the deprecated way to specify validate_checksum
if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args:
result['msg'] = "validate_checksum and validate_md5 cannot both be specified"
# validate_md5 is the deprecated way to specify validate_checksum
if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args:
result['msg'] = "validate_checksum and validate_md5 cannot both be specified"
if 'validate_md5' in self._task.args:
display.deprecated('Use validate_checksum instead of validate_md5', version='2.8')
if 'validate_md5' in self._task.args:
display.deprecated('Use validate_checksum instead of validate_md5', version='2.8')
if source is None or dest is None:
result['msg'] = "src and dest are required"
if source is None or dest is None:
result['msg'] = "src and dest are required"
if result.get('msg'):
result['failed'] = True
return result
if result.get('msg'):
result['failed'] = True
return result
source = self._connection._shell.join_path(source)
source = self._remote_expand_user(source)
source = self._connection._shell.join_path(source)
source = self._remote_expand_user(source)
remote_checksum = None
if not self._play_context.become:
# calculate checksum for the remote file, don't bother if using become as slurp will be used
# Force remote_checksum to follow symlinks because fetch always follows symlinks
remote_checksum = self._remote_checksum(source, all_vars=task_vars, follow=True)
remote_checksum = None
if not self._play_context.become:
# calculate checksum for the remote file, don't bother if using become as slurp will be used
# Force remote_checksum to follow symlinks because fetch always follows symlinks
remote_checksum = self._remote_checksum(source, all_vars=task_vars, follow=True)
# use slurp if permissions are lacking or privilege escalation is needed
remote_data = None
if remote_checksum in ('1', '2', None):
slurpres = self._execute_module(module_name='slurp', module_args=dict(src=source), task_vars=task_vars, tmp=tmp)
if slurpres.get('failed'):
if not fail_on_missing and (slurpres.get('msg').startswith('file not found') or remote_checksum == '1'):
result['msg'] = "the remote file does not exist, not transferring, ignored"
result['file'] = source
result['changed'] = False
# use slurp if permissions are lacking or privilege escalation is needed
remote_data = None
if remote_checksum in ('1', '2', None):
slurpres = self._execute_module(module_name='slurp', module_args=dict(src=source), task_vars=task_vars, tmp=tmp)
if slurpres.get('failed'):
if not fail_on_missing and (slurpres.get('msg').startswith('file not found') or remote_checksum == '1'):
result['msg'] = "the remote file does not exist, not transferring, ignored"
result['file'] = source
result['changed'] = False
else:
result.update(slurpres)
return result
else:
result.update(slurpres)
if slurpres['encoding'] == 'base64':
remote_data = base64.b64decode(slurpres['content'])
if remote_data is not None:
remote_checksum = checksum_s(remote_data)
# the source path may have been expanded on the
# target system, so we compare it here and use the
# expanded version if it's different
remote_source = slurpres.get('source')
if remote_source and remote_source != source:
source = remote_source
# calculate the destination name
if os.path.sep not in self._connection._shell.join_path('a', ''):
source = self._connection._shell._unquote(source)
source_local = source.replace('\\', '/')
else:
source_local = source
dest = os.path.expanduser(dest)
if flat:
if os.path.isdir(to_bytes(dest, errors='surrogate_or_strict')) and not dest.endswith(os.sep):
result['msg'] = "dest is an existing directory, use a trailing slash if you want to fetch src into that directory"
result['file'] = dest
result['failed'] = True
return result
if dest.endswith(os.sep):
# if the path ends with "/", we'll use the source filename as the
# destination filename
base = os.path.basename(source_local)
dest = os.path.join(dest, base)
if not dest.startswith("/"):
# if dest does not start with "/", we'll assume a relative path
dest = self._loader.path_dwim(dest)
else:
# files are saved in dest dir, with a subdir for each host, then the filename
if 'inventory_hostname' in task_vars:
target_name = task_vars['inventory_hostname']
else:
target_name = self._play_context.remote_addr
dest = "%s/%s/%s" % (self._loader.path_dwim(dest), target_name, source_local)
dest = dest.replace("//", "/")
if remote_checksum in ('0', '1', '2', '3', '4', '5'):
result['changed'] = False
result['file'] = source
if remote_checksum == '0':
result['msg'] = "unable to calculate the checksum of the remote file"
elif remote_checksum == '1':
result['msg'] = "the remote file does not exist"
elif remote_checksum == '2':
result['msg'] = "no read permission on remote file"
elif remote_checksum == '3':
result['msg'] = "remote file is a directory, fetch cannot work on directories"
elif remote_checksum == '4':
result['msg'] = "python isn't present on the system. Unable to compute checksum"
elif remote_checksum == '5':
result['msg'] = "stdlib json or simplejson was not found on the remote machine. Only the raw module can work without those installed"
# Historically, these don't fail because you may want to transfer
# a log file that possibly MAY exist but keep going to fetch other
# log files. Today, this is better achieved by adding
# ignore_errors or failed_when to the task. Control the behaviour
# via fail_when_missing
if fail_on_missing:
result['failed'] = True
del result['changed']
else:
result['msg'] += ", not transferring, ignored"
return result
else:
if slurpres['encoding'] == 'base64':
remote_data = base64.b64decode(slurpres['content'])
if remote_data is not None:
remote_checksum = checksum_s(remote_data)
# the source path may have been expanded on the
# target system, so we compare it here and use the
# expanded version if it's different
remote_source = slurpres.get('source')
if remote_source and remote_source != source:
source = remote_source
# calculate the destination name
if os.path.sep not in self._connection._shell.join_path('a', ''):
source = self._connection._shell._unquote(source)
source_local = source.replace('\\', '/')
else:
source_local = source
# calculate checksum for the local file
local_checksum = checksum(dest)
dest = os.path.expanduser(dest)
if flat:
if os.path.isdir(to_bytes(dest, errors='surrogate_or_strict')) and not dest.endswith(os.sep):
result['msg'] = "dest is an existing directory, use a trailing slash if you want to fetch src into that directory"
result['file'] = dest
result['failed'] = True
return result
if dest.endswith(os.sep):
# if the path ends with "/", we'll use the source filename as the
# destination filename
base = os.path.basename(source_local)
dest = os.path.join(dest, base)
if not dest.startswith("/"):
# if dest does not start with "/", we'll assume a relative path
dest = self._loader.path_dwim(dest)
else:
# files are saved in dest dir, with a subdir for each host, then the filename
if 'inventory_hostname' in task_vars:
target_name = task_vars['inventory_hostname']
else:
target_name = self._play_context.remote_addr
dest = "%s/%s/%s" % (self._loader.path_dwim(dest), target_name, source_local)
if remote_checksum != local_checksum:
# create the containing directories, if needed
makedirs_safe(os.path.dirname(dest))
dest = dest.replace("//", "/")
if remote_checksum in ('0', '1', '2', '3', '4', '5'):
result['changed'] = False
result['file'] = source
if remote_checksum == '0':
result['msg'] = "unable to calculate the checksum of the remote file"
elif remote_checksum == '1':
result['msg'] = "the remote file does not exist"
elif remote_checksum == '2':
result['msg'] = "no read permission on remote file"
elif remote_checksum == '3':
result['msg'] = "remote file is a directory, fetch cannot work on directories"
elif remote_checksum == '4':
result['msg'] = "python isn't present on the system. Unable to compute checksum"
elif remote_checksum == '5':
result['msg'] = "stdlib json or simplejson was not found on the remote machine. Only the raw module can work without those installed"
# Historically, these don't fail because you may want to transfer
# a log file that possibly MAY exist but keep going to fetch other
# log files. Today, this is better achieved by adding
# ignore_errors or failed_when to the task. Control the behaviour
# via fail_when_missing
if fail_on_missing:
result['failed'] = True
del result['changed']
else:
result['msg'] += ", not transferring, ignored"
return result
# calculate checksum for the local file
local_checksum = checksum(dest)
if remote_checksum != local_checksum:
# create the containing directories, if needed
makedirs_safe(os.path.dirname(dest))
# fetch the file and check for changes
if remote_data is None:
self._connection.fetch_file(source, dest)
else:
# fetch the file and check for changes
if remote_data is None:
self._connection.fetch_file(source, dest)
else:
try:
f = open(to_bytes(dest, errors='surrogate_or_strict'), 'wb')
f.write(remote_data)
f.close()
except (IOError, OSError) as e:
raise AnsibleError("Failed to fetch the file: %s" % e)
new_checksum = secure_hash(dest)
# For backwards compatibility. We'll return None on FIPS enabled systems
try:
f = open(to_bytes(dest, errors='surrogate_or_strict'), 'wb')
f.write(remote_data)
f.close()
except (IOError, OSError) as e:
raise AnsibleError("Failed to fetch the file: %s" % e)
new_checksum = secure_hash(dest)
# For backwards compatibility. We'll return None on FIPS enabled systems
try:
new_md5 = md5(dest)
except ValueError:
new_md5 = None
new_md5 = md5(dest)
except ValueError:
new_md5 = None
if validate_checksum and new_checksum != remote_checksum:
result.update(dict(failed=True, md5sum=new_md5,
msg="checksum mismatch", file=source, dest=dest, remote_md5sum=None,
checksum=new_checksum, remote_checksum=remote_checksum))
if validate_checksum and new_checksum != remote_checksum:
result.update(dict(failed=True, md5sum=new_md5,
msg="checksum mismatch", file=source, dest=dest, remote_md5sum=None,
checksum=new_checksum, remote_checksum=remote_checksum))
else:
result.update({'changed': True, 'md5sum': new_md5, 'dest': dest,
'remote_md5sum': None, 'checksum': new_checksum,
'remote_checksum': remote_checksum})
else:
result.update({'changed': True, 'md5sum': new_md5, 'dest': dest,
'remote_md5sum': None, 'checksum': new_checksum,
'remote_checksum': remote_checksum})
else:
# For backwards compatibility. We'll return None on FIPS enabled systems
try:
local_md5 = md5(dest)
except ValueError:
local_md5 = None
result.update(dict(changed=False, md5sum=local_md5, file=source, dest=dest, checksum=local_checksum))
# For backwards compatibility. We'll return None on FIPS enabled systems
try:
local_md5 = md5(dest)
except ValueError:
local_md5 = None
result.update(dict(changed=False, md5sum=local_md5, file=source, dest=dest, checksum=local_checksum))
finally:
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -29,24 +29,28 @@ class ActionModule(ActionBase):
self._supports_check_mode = True
self._supports_async = True
results = super(ActionModule, self).run(tmp, task_vars)
result = super(ActionModule, self).run(tmp, task_vars)
if not results.get('skipped'):
if not result.get('skipped'):
if results.get('invocation', {}).get('module_args'):
if result.get('invocation', {}).get('module_args'):
# avoid passing to modules in case of no_log
# should not be set anymore but here for backwards compatibility
del results['invocation']['module_args']
del result['invocation']['module_args']
# FUTURE: better to let _execute_module calculate this internally?
wrap_async = self._task.async_val and not self._connection.has_native_async
# do work!
results = merge_hash(results, self._execute_module(tmp=tmp, task_vars=task_vars, wrap_async=wrap_async))
result = merge_hash(result, self._execute_module(tmp=tmp, task_vars=task_vars, wrap_async=wrap_async))
# hack to keep --verbose from showing all the setup module results
# moved from setup module as now we filter out all _ansible_ from results
# hack to keep --verbose from showing all the setup module result
# moved from setup module as now we filter out all _ansible_ from result
if self._task.action == 'setup':
results['_ansible_verbose_override'] = True
result['_ansible_verbose_override'] = True
return results
if not wrap_async:
# remove a temporary path we created
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -17,6 +17,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleAction, AnsibleActionFail
from ansible.plugins.action import ActionBase
try:
@@ -46,29 +47,35 @@ class ActionModule(ActionBase):
module = self._templar.template("{{hostvars['%s']['ansible_facts']['pkg_mgr']}}" % self._task.delegate_to)
else:
module = self._templar.template('{{ansible_facts.pkg_mgr}}')
except:
except Exception:
pass # could not get it from template!
if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_pkg_mgr', gather_subset='!all'), task_vars=task_vars)
display.debug("Facts %s" % facts)
module = facts.get('ansible_facts', {}).get('ansible_pkg_mgr', 'auto')
try:
if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_pkg_mgr', gather_subset='!all'), task_vars=task_vars)
display.debug("Facts %s" % facts)
module = facts.get('ansible_facts', {}).get('ansible_pkg_mgr', 'auto')
if module != 'auto':
if module != 'auto':
if module not in self._shared_loader_obj.module_loader:
result['failed'] = True
result['msg'] = 'Could not find a module for %s.' % module
if module not in self._shared_loader_obj.module_loader:
raise AnsibleActionFail('Could not find a module for %s.' % module)
else:
# run the 'package' module
new_module_args = self._task.args.copy()
if 'use' in new_module_args:
del new_module_args['use']
display.vvvv("Running %s" % module)
result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val))
else:
# run the 'package' module
new_module_args = self._task.args.copy()
if 'use' in new_module_args:
del new_module_args['use']
raise AnsibleActionFail('Could not detect which package manager to use. Try gathering facts or setting the "use" option.')
display.vvvv("Running %s" % module)
result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val))
else:
result['failed'] = True
result['msg'] = 'Could not detect which package manager to use. Try gathering facts or setting the "use" option.'
except AnsibleAction as e:
result.update(e.result)
finally:
if not self._task.async_val:
# remove a temporary path we created
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -20,7 +20,7 @@ __metaclass__ = type
import os
from ansible.errors import AnsibleError
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
@@ -28,6 +28,8 @@ 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()
@@ -37,39 +39,33 @@ class ActionModule(ActionBase):
src = self._task.args.get('src', None)
remote_src = boolean(self._task.args.get('remote_src', 'no'), strict=False)
if src is None:
result['failed'] = True
result['msg'] = "src is required"
return result
elif remote_src:
# everything is remote, so we just execute the module
# without changing any of the module arguments
result.update(self._execute_module(task_vars=task_vars))
return result
try:
src = self._find_needle('files', src)
except AnsibleError as e:
result['failed'] = True
result['msg'] = to_native(e)
return result
if src is None:
raise AnsibleActionFail("src is required")
elif remote_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))
# create the remote tmp dir if needed, and put the source file there
if tmp is None or "-tmp-" not in tmp:
tmp = self._make_tmp_path()
try:
src = self._find_needle('files', src)
except AnsibleError as e:
raise AnsibleActionFail(to_native(e))
tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src))
self._transfer_file(src, tmp_src)
tmp_src = self._connection._shell.join_path(self._connection._shell.tempdir, os.path.basename(src))
self._transfer_file(src, tmp_src)
self._fixup_perms2((tmp_src,))
self._fixup_perms2((tmp, tmp_src))
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
src=tmp_src,
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
src=tmp_src,
)
)
)
result.update(self._execute_module('patch', module_args=new_module_args, task_vars=task_vars))
self._remove_tmp_path(tmp)
result.update(self._execute_module('patch', 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.tempdir)
return result

View File

@@ -21,12 +21,13 @@ import os
import re
import shlex
from ansible.errors import AnsibleError
from ansible.errors import AnsibleError, AnsibleAction, AnsibleActionDone, AnsibleActionFail, AnsibleActionSkip
from ansible.module_utils._text import to_native, to_text
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = True
# On Windows platform, absolute paths begin with a (back)slash
@@ -40,95 +41,91 @@ class ActionModule(ActionBase):
result = super(ActionModule, self).run(tmp, task_vars)
if not tmp:
tmp = self._make_tmp_path()
creates = self._task.args.get('creates')
if creates:
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
if self._remote_file_exists(creates):
self._remove_tmp_path(tmp)
return dict(skipped=True, msg=("skipped, since %s exists" % creates))
removes = self._task.args.get('removes')
if removes:
# do not run the command if the line contains removes=filename
# and the filename does not exist. This allows idempotence
# of command executions.
if not self._remote_file_exists(removes):
self._remove_tmp_path(tmp)
return dict(skipped=True, msg=("skipped, since %s does not exist" % removes))
# The chdir must be absolute, because a relative path would rely on
# remote node behaviour & user config.
chdir = self._task.args.get('chdir')
if chdir:
# Powershell is the only Windows-path aware shell
if self._connection._shell.SHELL_FAMILY == 'powershell' and \
not self.windows_absolute_path_detection.matches(chdir):
return dict(failed=True, msg='chdir %s must be an absolute path for a Windows remote node' % chdir)
# Every other shell is unix-path-aware.
if self._connection._shell.SHELL_FAMILY != 'powershell' and not chdir.startswith('/'):
return dict(failed=True, msg='chdir %s must be an absolute path for a Unix-aware remote node' % chdir)
# Split out the script as the first item in raw_params using
# shlex.split() in order to support paths and files with spaces in the name.
# Any arguments passed to the script will be added back later.
raw_params = to_native(self._task.args.get('_raw_params', ''), errors='surrogate_or_strict')
parts = [to_text(s, errors='surrogate_or_strict') for s in shlex.split(raw_params.strip())]
source = parts[0]
try:
source = self._loader.get_real_file(self._find_needle('files', source), decrypt=self._task.args.get('decrypt', True))
except AnsibleError as e:
return dict(failed=True, msg=to_native(e))
creates = self._task.args.get('creates')
if creates:
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
if self._remote_file_exists(creates):
raise AnsibleActionSkip("%s exists, matching creates option" % creates)
if not self._play_context.check_mode:
# transfer the file to a remote tmp location
tmp_src = self._connection._shell.join_path(tmp, os.path.basename(source))
removes = self._task.args.get('removes')
if removes:
# do not run the command if the line contains removes=filename
# and the filename does not exist. This allows idempotence
# of command executions.
if not self._remote_file_exists(removes):
raise AnsibleActionSkip("%s does not exist, matching removes option" % removes)
# Convert raw_params to text for the purpose of replacing the script since
# parts and tmp_src are both unicode strings and raw_params will be different
# depending on Python version.
#
# Once everything is encoded consistently, replace the script path on the remote
# system with the remainder of the raw_params. This preserves quoting in parameters
# that would have been removed by shlex.split().
target_command = to_text(raw_params).strip().replace(parts[0], tmp_src)
# The chdir must be absolute, because a relative path would rely on
# remote node behaviour & user config.
chdir = self._task.args.get('chdir')
if chdir:
# Powershell is the only Windows-path aware shell
if self._connection._shell.SHELL_FAMILY == 'powershell' and \
not self.windows_absolute_path_detection.matches(chdir):
raise AnsibleActionFail('chdir %s must be an absolute path for a Windows remote node' % chdir)
# Every other shell is unix-path-aware.
if self._connection._shell.SHELL_FAMILY != 'powershell' and not chdir.startswith('/'):
raise AnsibleActionFail('chdir %s must be an absolute path for a Unix-aware remote node' % chdir)
self._transfer_file(source, tmp_src)
# Split out the script as the first item in raw_params using
# shlex.split() in order to support paths and files with spaces in the name.
# Any arguments passed to the script will be added back later.
raw_params = to_native(self._task.args.get('_raw_params', ''), errors='surrogate_or_strict')
parts = [to_text(s, errors='surrogate_or_strict') for s in shlex.split(raw_params.strip())]
source = parts[0]
# set file permissions, more permissive when the copy is done as a different user
self._fixup_perms2((tmp, tmp_src), execute=True)
try:
source = self._loader.get_real_file(self._find_needle('files', source), decrypt=self._task.args.get('decrypt', True))
except AnsibleError as e:
raise AnsibleActionFail(to_native(e))
# add preparation steps to one ssh roundtrip executing the script
env_dict = dict()
env_string = self._compute_environment_string(env_dict)
script_cmd = ' '.join([env_string, target_command])
if self._play_context.check_mode:
# now we execute script, always assume changed.
result['changed'] = True
self._remove_tmp_path(tmp)
return result
script_cmd = self._connection._shell.wrap_for_exec(script_cmd)
if not self._play_context.check_mode:
# transfer the file to a remote tmp location
tmp_src = self._connection._shell.join_path(self._connection._shell.tempdir, os.path.basename(source))
exec_data = None
# HACK: come up with a sane way to pass around env outside the command
if self._connection.transport == "winrm":
exec_data = self._connection._create_raw_wrapper_payload(script_cmd, env_dict)
# Convert raw_params to text for the purpose of replacing the script since
# parts and tmp_src are both unicode strings and raw_params will be different
# depending on Python version.
#
# Once everything is encoded consistently, replace the script path on the remote
# system with the remainder of the raw_params. This preserves quoting in parameters
# that would have been removed by shlex.split().
target_command = to_text(raw_params).strip().replace(parts[0], tmp_src)
result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir))
self._transfer_file(source, tmp_src)
# clean up after
self._remove_tmp_path(tmp)
# set file permissions, more permissive when the copy is done as a different user
self._fixup_perms2((tmp_src,), execute=True)
result['changed'] = True
# add preparation steps to one ssh roundtrip executing the script
env_dict = dict()
env_string = self._compute_environment_string(env_dict)
script_cmd = ' '.join([env_string, target_command])
if 'rc' in result and result['rc'] != 0:
result['failed'] = True
result['msg'] = 'non-zero return code'
if self._play_context.check_mode:
raise AnsibleActionDone()
script_cmd = self._connection._shell.wrap_for_exec(script_cmd)
exec_data = None
# HACK: come up with a sane way to pass around env outside the command
if self._connection.transport == "winrm":
exec_data = self._connection._create_raw_wrapper_payload(script_cmd, env_dict)
result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir))
if 'rc' in result and result['rc'] != 0:
raise AnsibleActionFail('non-zero return code')
except AnsibleAction as e:
result.update(e.result)
finally:
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -18,6 +18,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleAction, AnsibleActionFail
from ansible.plugins.action import ActionBase
@@ -48,35 +49,41 @@ class ActionModule(ActionBase):
except:
pass # could not get it from template!
if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(gather_subset='!all', filter='ansible_service_mgr'), task_vars=task_vars)
self._display.debug("Facts %s" % facts)
module = facts.get('ansible_facts', {}).get('ansible_service_mgr', 'auto')
try:
if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(gather_subset='!all', filter='ansible_service_mgr'), task_vars=task_vars)
self._display.debug("Facts %s" % facts)
module = facts.get('ansible_facts', {}).get('ansible_service_mgr', 'auto')
if not module or module == 'auto' or module not in self._shared_loader_obj.module_loader:
module = 'service'
if not module or module == 'auto' or module not in self._shared_loader_obj.module_loader:
module = 'service'
if module != 'auto':
# run the 'service' module
new_module_args = self._task.args.copy()
if 'use' in new_module_args:
del new_module_args['use']
if module != 'auto':
# run the 'service' module
new_module_args = self._task.args.copy()
if 'use' in new_module_args:
del new_module_args['use']
# for backwards compatibility
if 'state' in new_module_args and new_module_args['state'] == 'running':
self._display.deprecated(msg="state=running is deprecated. Please use state=started", version="2.7")
new_module_args['state'] = 'started'
# for backwards compatibility
if 'state' in new_module_args and new_module_args['state'] == 'running':
self._display.deprecated(msg="state=running is deprecated. Please use state=started", version="2.7")
new_module_args['state'] = 'started'
if module in self.UNUSED_PARAMS:
for unused in self.UNUSED_PARAMS[module]:
if unused in new_module_args:
del new_module_args[unused]
self._display.warning('Ignoring "%s" as it is not used in "%s"' % (unused, module))
if module in self.UNUSED_PARAMS:
for unused in self.UNUSED_PARAMS[module]:
if unused in new_module_args:
del new_module_args[unused]
self._display.warning('Ignoring "%s" as it is not used in "%s"' % (unused, module))
self._display.vvvv("Running %s" % module)
result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val))
else:
result['failed'] = True
result['msg'] = 'Could not detect which service manager to use. Try gathering facts or setting the "use" option.'
self._display.vvvv("Running %s" % module)
result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val))
else:
raise AnsibleActionFail('Could not detect which service manager to use. Try gathering facts or setting the "use" option.')
except AnsibleAction as e:
result.update(e.result)
finally:
if not self._task.async_val:
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -22,4 +22,9 @@ class ActionModule(ActionBase):
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj)
return command_action.run(task_vars=task_vars)
result = command_action.run(task_vars=task_vars)
# remove a temporary path we created
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -21,13 +21,11 @@ import os
import shutil
import tempfile
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
from ansible.template import generate_ansible_template_vars
from ansible.utils.hashing import checksum_s
class ActionModule(ActionBase):
@@ -35,20 +33,6 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = True
DEFAULT_NEWLINE_SEQUENCE = "\n"
def get_checksum(self, dest, all_vars, try_directory=False, source=None, tmp=None):
try:
dest_stat = self._execute_remote_stat(dest, all_vars=all_vars, follow=False, tmp=tmp)
if dest_stat['exists'] and dest_stat['isdir'] and try_directory and source:
base = os.path.basename(source)
dest = os.path.join(dest, base)
dest_stat = self._execute_remote_stat(dest, all_vars=all_vars, follow=False, tmp=tmp)
except AnsibleError as e:
return dict(failed=True, msg=to_text(e))
return dest_stat['checksum']
def run(self, tmp=None, task_vars=None):
''' handler for template operations '''
@@ -76,108 +60,103 @@ class ActionModule(ActionBase):
if newline_sequence in wrong_sequences:
newline_sequence = allowed_sequences[wrong_sequences.index(newline_sequence)]
if state is not None:
result['failed'] = True
result['msg'] = "'state' cannot be specified on a template"
elif source is None or dest is None:
result['failed'] = True
result['msg'] = "src and dest are required"
elif newline_sequence not in allowed_sequences:
result['failed'] = True
result['msg'] = "newline_sequence needs to be one of: \n, \r or \r\n"
else:
try:
if state is not None:
raise AnsibleActionFail("'state' cannot be specified on a template")
elif source is None or dest is None:
raise AnsibleActionFail("src and dest are required")
elif newline_sequence not in allowed_sequences:
raise AnsibleActionFail("newline_sequence needs to be one of: \n, \r or \r\n")
else:
try:
source = self._find_needle('templates', source)
except AnsibleError as e:
raise AnsibleActionFail(to_text(e))
# Get vault decrypted tmp file
try:
source = self._find_needle('templates', source)
except AnsibleError as e:
result['failed'] = True
result['msg'] = to_text(e)
tmp_source = self._loader.get_real_file(source)
except AnsibleFileNotFound as e:
raise AnsibleActionFail("could not find src=%s, %s" % (source, to_text(e)))
if 'failed' in result:
return result
# template the source data locally & get ready to transfer
try:
with open(tmp_source, 'r') as f:
template_data = to_text(f.read())
# Get vault decrypted tmp file
try:
tmp_source = self._loader.get_real_file(source)
except AnsibleFileNotFound as e:
result['failed'] = True
result['msg'] = "could not find src=%s, %s" % (source, e)
self._remove_tmp_path(tmp)
return result
# set jinja2 internal search path for includes
searchpath = task_vars.get('ansible_search_path', [])
searchpath.extend([self._loader._basedir, os.path.dirname(source)])
# template the source data locally & get ready to transfer
try:
with open(tmp_source, 'r') as f:
template_data = to_text(f.read())
# We want to search into the 'templates' subdir of each search path in
# addition to our original search paths.
newsearchpath = []
for p in searchpath:
newsearchpath.append(os.path.join(p, 'templates'))
newsearchpath.append(p)
searchpath = newsearchpath
# set jinja2 internal search path for includes
searchpath = task_vars.get('ansible_search_path', [])
searchpath.extend([self._loader._basedir, os.path.dirname(source)])
self._templar.environment.loader.searchpath = searchpath
self._templar.environment.newline_sequence = newline_sequence
if block_start_string is not None:
self._templar.environment.block_start_string = block_start_string
if block_end_string is not None:
self._templar.environment.block_end_string = block_end_string
if variable_start_string is not None:
self._templar.environment.variable_start_string = variable_start_string
if variable_end_string is not None:
self._templar.environment.variable_end_string = variable_end_string
if trim_blocks is not None:
self._templar.environment.trim_blocks = bool(trim_blocks)
# We want to search into the 'templates' subdir of each search path in
# addition to our original search paths.
newsearchpath = []
for p in searchpath:
newsearchpath.append(os.path.join(p, 'templates'))
newsearchpath.append(p)
searchpath = newsearchpath
# add ansible 'template' vars
temp_vars = task_vars.copy()
temp_vars.update(generate_ansible_template_vars(source))
self._templar.environment.loader.searchpath = searchpath
self._templar.environment.newline_sequence = newline_sequence
if block_start_string is not None:
self._templar.environment.block_start_string = block_start_string
if block_end_string is not None:
self._templar.environment.block_end_string = block_end_string
if variable_start_string is not None:
self._templar.environment.variable_start_string = variable_start_string
if variable_end_string is not None:
self._templar.environment.variable_end_string = variable_end_string
if trim_blocks is not None:
self._templar.environment.trim_blocks = bool(trim_blocks)
old_vars = self._templar._available_variables
self._templar.set_available_variables(temp_vars)
resultant = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
self._templar.set_available_variables(old_vars)
except AnsibleAction:
raise
except Exception as e:
raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
finally:
self._loader.cleanup_tmp_file(tmp_source)
# add ansible 'template' vars
temp_vars = task_vars.copy()
temp_vars.update(generate_ansible_template_vars(source))
new_task = self._task.copy()
new_task.args.pop('newline_sequence', None)
new_task.args.pop('block_start_string', None)
new_task.args.pop('block_end_string', None)
new_task.args.pop('variable_start_string', None)
new_task.args.pop('variable_end_string', None)
new_task.args.pop('trim_blocks', None)
try:
tempdir = tempfile.mkdtemp()
result_file = os.path.join(tempdir, os.path.basename(source))
with open(result_file, 'wb') as f:
f.write(to_bytes(resultant, errors='surrogate_or_strict'))
old_vars = self._templar._available_variables
self._templar.set_available_variables(temp_vars)
resultant = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
self._templar.set_available_variables(old_vars)
except Exception as e:
result['failed'] = True
result['msg'] = "%s: %s" % (type(e).__name__, to_text(e))
return result
new_task.args.update(
dict(
src=result_file,
dest=dest,
follow=follow,
),
)
copy_action = self._shared_loader_obj.action_loader.get('copy',
task=new_task,
connection=self._connection,
play_context=self._play_context,
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj)
result.update(copy_action.run(task_vars=task_vars))
finally:
shutil.rmtree(tempdir)
except AnsibleAction as e:
result.update(e.result)
finally:
self._loader.cleanup_tmp_file(tmp_source)
new_task = self._task.copy()
new_task.args.pop('newline_sequence', None)
new_task.args.pop('block_start_string', None)
new_task.args.pop('block_end_string', None)
new_task.args.pop('variable_start_string', None)
new_task.args.pop('variable_end_string', None)
new_task.args.pop('trim_blocks', None)
try:
tempdir = tempfile.mkdtemp()
result_file = os.path.join(tempdir, os.path.basename(source))
with open(result_file, 'wb') as f:
f.write(to_bytes(resultant, errors='surrogate_or_strict'))
new_task.args.update(
dict(
src=result_file,
dest=dest,
follow=follow,
),
)
copy_action = self._shared_loader_obj.action_loader.get('copy',
task=new_task,
connection=self._connection,
play_context=self._play_context,
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj)
result.update(copy_action.run(task_vars=task_vars))
finally:
shutil.rmtree(tempdir)
self._remove_tmp_path(self._connection._shell.tempdir)
return result

View File

@@ -20,7 +20,7 @@ __metaclass__ = type
import os
from ansible.errors import AnsibleError
from ansible.errors import AnsibleError, AnsibleAction, AnsibleActionFail, AnsibleActionSkip
from ansible.module_utils._text import to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
@@ -43,96 +43,81 @@ class ActionModule(ActionBase):
creates = self._task.args.get('creates', None)
decrypt = self._task.args.get('decrypt', True)
# "copy" is deprecated in favor of "remote_src".
if 'copy' in self._task.args:
# They are mutually exclusive.
if 'remote_src' in self._task.args:
result['failed'] = True
result['msg'] = "parameters are mutually exclusive: ('copy', 'remote_src')"
return result
# We will take the information from copy and store it in
# the remote_src var to use later in this file.
self._task.args['remote_src'] = remote_src = not boolean(self._task.args.pop('copy'), strict=False)
if source is None or dest is None:
result['failed'] = True
result['msg'] = "src (or content) and dest are required"
return result
if not tmp:
tmp = self._make_tmp_path()
if creates:
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
creates = self._remote_expand_user(creates)
if self._remote_file_exists(creates):
result['skipped'] = True
result['msg'] = "skipped, since %s exists" % creates
self._remove_tmp_path(tmp)
return result
dest = self._remote_expand_user(dest) # CCTODO: Fix path for Windows hosts.
source = os.path.expanduser(source)
if not remote_src:
try:
source = self._loader.get_real_file(self._find_needle('files', source), decrypt=decrypt)
except AnsibleError as e:
result['failed'] = True
result['msg'] = to_text(e)
self._remove_tmp_path(tmp)
return result
try:
remote_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=True)
except AnsibleError as e:
result['failed'] = True
result['msg'] = to_text(e)
self._remove_tmp_path(tmp)
return result
# "copy" is deprecated in favor of "remote_src".
if 'copy' in self._task.args:
# They are mutually exclusive.
if 'remote_src' in self._task.args:
raise AnsibleActionFail("parameters are mutually exclusive: ('copy', 'remote_src')")
# We will take the information from copy and store it in
# the remote_src var to use later in this file.
self._task.args['remote_src'] = remote_src = not boolean(self._task.args.pop('copy'), strict=False)
if not remote_stat['exists'] or not remote_stat['isdir']:
result['failed'] = True
result['msg'] = "dest '%s' must be an existing dir" % dest
self._remove_tmp_path(tmp)
return result
if source is None or dest is None:
raise AnsibleActionFail("src (or content) and dest are required")
if not remote_src:
# transfer the file to a remote tmp location
tmp_src = self._connection._shell.join_path(tmp, 'source')
self._transfer_file(source, tmp_src)
if creates:
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
creates = self._remote_expand_user(creates)
if self._remote_file_exists(creates):
raise AnsibleActionSkip("skipped, since %s exists" % creates)
# handle diff mode client side
# handle check mode client side
dest = self._remote_expand_user(dest) # CCTODO: Fix path for Windows hosts.
source = os.path.expanduser(source)
if not remote_src:
# fix file permissions when the copy is done as a different user
self._fixup_perms2((tmp, tmp_src))
# Build temporary module_args.
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
src=tmp_src,
original_basename=os.path.basename(source),
),
)
if not remote_src:
try:
source = self._loader.get_real_file(self._find_needle('files', source), decrypt=decrypt)
except AnsibleError as e:
raise AnsibleActionFail(to_text(e))
else:
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
original_basename=os.path.basename(source),
),
)
try:
remote_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=True)
except AnsibleError as e:
raise AnsibleActionFail(to_text(e))
# remove action plugin only key
for key in ('decrypt',):
if key in new_module_args:
del new_module_args[key]
if not remote_stat['exists'] or not remote_stat['isdir']:
raise AnsibleActionFail("dest '%s' must be an existing dir" % dest)
# execute the unarchive module now, with the updated args
result.update(self._execute_module(module_args=new_module_args, task_vars=task_vars))
self._remove_tmp_path(tmp)
if not remote_src:
# transfer the file to a remote tmp location
tmp_src = self._connection._shell.join_path(self._connection._shell.tempdir, 'source')
self._transfer_file(source, tmp_src)
# handle diff mode client side
# handle check mode client side
if not remote_src:
# fix file permissions when the copy is done as a different user
self._fixup_perms2((self._connection._shell.tempdir, tmp_src))
# Build temporary module_args.
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
src=tmp_src,
original_basename=os.path.basename(source),
),
)
else:
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
original_basename=os.path.basename(source),
),
)
# remove action plugin only key
for key in ('decrypt',):
if key in new_module_args:
del new_module_args[key]
# execute the unarchive module now, with the updated args
result.update(self._execute_module(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.tempdir)
return result

View File

@@ -15,6 +15,7 @@ import tempfile
import traceback
import zipfile
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
@@ -218,7 +219,7 @@ class ActionModule(ActionBase):
def _create_content_tempfile(self, content):
''' Create a tempfile containing defined content '''
fd, content_tempfile = tempfile.mkstemp()
fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP)
f = os.fdopen(fd, 'wb')
content = to_bytes(content)
try: