Two fixes to action plugins

* Fix the task_vars parameter to not default to a mutable type (dict)
* Implement invocation in the base class's run() method have each action
  module call the run() method's implemention in the base class.
* Return values from the action plugins' run() method takes the return
  value from the base class run() method into account so that invocation
  makes its way to the output.

Fixes #12869
This commit is contained in:
Toshio Kuratomi 2015-10-22 16:07:26 -07:00
parent 75cff7129c
commit 2e87c1f74e
27 changed files with 387 additions and 179 deletions

View file

@ -27,8 +27,9 @@ import random
import stat import stat
import tempfile import tempfile
import time import time
from abc import ABCMeta, abstractmethod
from ansible.compat.six import binary_type, text_type, iteritems from ansible.compat.six import binary_type, text_type, iteritems, with_metaclass
from ansible import constants as C from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleConnectionFailure from ansible.errors import AnsibleError, AnsibleConnectionFailure
@ -42,7 +43,7 @@ except ImportError:
from ansible.utils.display import Display from ansible.utils.display import Display
display = Display() display = Display()
class ActionBase: class ActionBase(with_metaclass(ABCMeta, object)):
''' '''
This class is the base class for all action plugins, and defines This class is the base class for all action plugins, and defines
@ -62,11 +63,39 @@ class ActionBase:
self._supports_check_mode = True self._supports_check_mode = True
def _configure_module(self, module_name, module_args, task_vars=dict()): @abstractmethod
def run(self, tmp=None, task_vars=None):
""" Action Plugins should implement this method to perform their
tasks. Everything else in this base class is a helper method for the
action plugin to do that.
:kwarg tmp: Temporary directory. Sometimes an action plugin sets up
a temporary directory and then calls another module. This parameter
allows us to reuse the same directory for both.
:kwarg task_vars: The variables (host vars, group vars, config vars,
etc) associated with this task.
:returns: dictionary of results from the module
Implementors of action modules may find the following variables especially useful:
* Module parameters. These are stored in self._task.args
"""
# store the module invocation details into the results
results = {}
if self._task.async == 0:
results['invocation'] = dict(
module_name = self._task.action,
module_args = self._task.args,
)
return results
def _configure_module(self, module_name, module_args, task_vars=None):
''' '''
Handles the loading and templating of the module code through the Handles the loading and templating of the module code through the
modify_module() function. modify_module() function.
''' '''
if task_vars is None:
task_vars = dict()
# Search module path(s) for named module. # Search module path(s) for named module.
for mod_type in self._connection.module_implementation_preferences: for mod_type in self._connection.module_implementation_preferences:
@ -329,10 +358,12 @@ class ActionBase:
return data[idx:] return data[idx:]
def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=dict(), persist_files=False, delete_remote_tmp=True): def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=True):
''' '''
Transfer and run a module along with its arguments. Transfer and run a module along with its arguments.
''' '''
if task_vars is None:
task_vars = dict()
# if a module name was not specified for this execution, use # if a module name was not specified for this execution, use
# the action from the task # the action from the task
@ -441,13 +472,6 @@ class ActionBase:
if 'stdout' in data and 'stdout_lines' not in data: if 'stdout' in data and 'stdout_lines' not in data:
data['stdout_lines'] = data.get('stdout', u'').splitlines() data['stdout_lines'] = data.get('stdout', u'').splitlines()
# store the module invocation details back into the result
if self._task.async == 0:
data['invocation'] = dict(
module_args = module_args,
module_name = module_name,
)
self._display.debug("done with _execute_module (%s, %s)" % (module_name, module_args)) self._display.debug("done with _execute_module (%s, %s)" % (module_name, module_args))
return data return data

View file

@ -20,27 +20,32 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import re
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.parsing.utils.addresses import parse_address from ansible.parsing.utils.addresses import parse_address
from ansible.errors import AnsibleError, AnsibleParserError from ansible.errors import AnsibleError
class ActionModule(ActionBase): class ActionModule(ActionBase):
''' Create inventory hosts and groups in the memory inventory''' ''' Create inventory hosts and groups in the memory inventory'''
### We need to be able to modify the inventory # We need to be able to modify the inventory
BYPASS_HOST_LOOP = True BYPASS_HOST_LOOP = True
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
if self._play_context.check_mode: if self._play_context.check_mode:
return dict(skipped=True, msg='check mode not supported for this module') result['skipped'] = True
result['msg'] = 'check mode not supported for this module'
return result
# Parse out any hostname:port patterns # Parse out any hostname:port patterns
new_name = self._task.args.get('name', self._task.args.get('hostname', None)) new_name = self._task.args.get('name', self._task.args.get('hostname', None))
#vv("creating host via 'add_host': hostname=%s" % new_name) self._display.vv("creating host via 'add_host': hostname=%s" % new_name)
name, port = parse_address(new_name, allow_ranges=False) name, port = parse_address(new_name, allow_ranges=False)
if not name: if not name:
@ -48,7 +53,7 @@ class ActionModule(ActionBase):
if port: if port:
self._task.args['ansible_ssh_port'] = port self._task.args['ansible_ssh_port'] = port
groups = self._task.args.get('groupname', self._task.args.get('groups', self._task.args.get('group', ''))) groups = self._task.args.get('groupname', self._task.args.get('groups', self._task.args.get('group', '')))
# add it to the group if that was specified # add it to the group if that was specified
new_groups = [] new_groups = []
if groups: if groups:
@ -58,8 +63,11 @@ class ActionModule(ActionBase):
# Add any variables to the new_host # Add any variables to the new_host
host_vars = dict() host_vars = dict()
special_args = frozenset(('name', 'hostname', 'groupname', 'groups'))
for k in self._task.args.keys(): for k in self._task.args.keys():
if not k in [ 'name', 'hostname', 'groupname', 'groups' ]: if k not in special_args:
host_vars[k] = self._task.args[k] host_vars[k] = self._task.args[k]
return dict(changed=True, add_host=dict(host_name=name, groups=new_groups, host_vars=host_vars)) result['changed'] = True
result['add_host'] = dict(host_name=name, groups=new_groups, host_vars=host_vars)
return result

View file

@ -20,16 +20,14 @@ __metaclass__ = type
import os import os
import os.path import os.path
import pipes
import shutil
import tempfile import tempfile
import base64
import re import re
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
from ansible.utils.hashing import checksum_s from ansible.utils.hashing import checksum_s
class ActionModule(ActionBase): class ActionModule(ActionBase):
TRANSFERS_FILES = True TRANSFERS_FILES = True
@ -75,10 +73,16 @@ class ActionModule(ActionBase):
tmp.close() tmp.close()
return temp_path return temp_path
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
if self._play_context.check_mode: if self._play_context.check_mode:
return dict(skipped=True, msg=("skipped, this module does not support check_mode.")) result['skipped'] = True
result['msg'] = "skipped, this module does not support check_mode."
return result
src = self._task.args.get('src', None) src = self._task.args.get('src', None)
dest = self._task.args.get('dest', None) dest = self._task.args.get('dest', None)
@ -87,12 +91,15 @@ class ActionModule(ActionBase):
regexp = self._task.args.get('regexp', None) regexp = self._task.args.get('regexp', None)
ignore_hidden = self._task.args.get('ignore_hidden', False) ignore_hidden = self._task.args.get('ignore_hidden', False)
if src is None or dest is None: if src is None or dest is None:
return dict(failed=True, msg="src and dest are required") result['failed'] = True
result['msg'] = "src and dest are required"
return result
if boolean(remote_src): if boolean(remote_src):
return self._execute_module(tmp=tmp, task_vars=task_vars) result.update(self._execute_module(tmp=tmp, task_vars=task_vars))
return result
elif self._task._role is not None: elif self._task._role is not None:
src = self._loader.path_dwim_relative(self._task._role._role_path, 'files', src) src = self._loader.path_dwim_relative(self._task._role._role_path, 'files', src)
else: else:
@ -136,7 +143,8 @@ class ActionModule(ActionBase):
res = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp) res = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp)
if diff: if diff:
res['diff'] = diff res['diff'] = diff
return res result.update(res)
return result
else: else:
new_module_args = self._task.args.copy() new_module_args = self._task.args.copy()
new_module_args.update( new_module_args.update(
@ -147,4 +155,5 @@ class ActionModule(ActionBase):
) )
) )
return self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp) result.update(self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp))
return result

View file

@ -21,14 +21,19 @@ from ansible.errors import AnsibleError
from ansible.playbook.conditional import Conditional from ansible.playbook.conditional import Conditional
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
''' Fail with custom message ''' ''' Fail with custom message '''
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
if not 'that' in self._task.args: result = super(ActionModule, self).run(tmp, task_vars)
if 'that' not in self._task.args:
raise AnsibleError('conditional required in "that" string') raise AnsibleError('conditional required in "that" string')
msg = None msg = None
@ -38,7 +43,7 @@ class ActionModule(ActionBase):
# make sure the 'that' items are a list # make sure the 'that' items are a list
thats = self._task.args['that'] thats = self._task.args['that']
if not isinstance(thats, list): if not isinstance(thats, list):
thats = [ thats ] thats = [thats]
# Now we iterate over the that items, temporarily assigning them # Now we iterate over the that items, temporarily assigning them
# to the task's when value so we can evaluate the conditional using # to the task's when value so we can evaluate the conditional using
@ -47,19 +52,18 @@ class ActionModule(ActionBase):
# that value now # that value now
cond = Conditional(loader=self._loader) cond = Conditional(loader=self._loader)
for that in thats: for that in thats:
cond.when = [ that ] cond.when = [that]
test_result = cond.evaluate_conditional(templar=self._templar, all_vars=task_vars) test_result = cond.evaluate_conditional(templar=self._templar, all_vars=task_vars)
if not test_result: if not test_result:
result = dict( result['failed'] = True
failed = True, result['evaluated_to'] = test_result
evaluated_to = test_result, result['assertion'] = that
assertion = that,
)
if msg: if msg:
result['msg'] = msg result['msg'] = msg
return result return result
return dict(changed=False, msg='all assertions passed') result['changed'] = False
result['msg'] = 'all assertions passed'
return result

View file

@ -23,13 +23,20 @@ import random
from ansible import constants as C from ansible import constants as C
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' transfer the given module name, plus the async module, then run it ''' ''' transfer the given module name, plus the async module, then run it '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
if self._play_context.check_mode: if self._play_context.check_mode:
return dict(skipped=True, msg='check mode not supported for this module') result['skipped'] = True
result['msg'] = 'check mode not supported for this module'
return result
if not tmp: if not tmp:
tmp = self._make_tmp_path() tmp = self._make_tmp_path()
@ -60,7 +67,7 @@ class ActionModule(ActionBase):
async_jid = str(random.randint(0, 999999999999)) async_jid = str(random.randint(0, 999999999999))
async_cmd = " ".join([str(x) for x in [env_string, async_module_path, async_jid, async_limit, remote_module_path, argsfile]]) async_cmd = " ".join([str(x) for x in [env_string, async_module_path, async_jid, async_limit, remote_module_path, argsfile]])
result = self._low_level_execute_command(cmd=async_cmd) result.update(self._low_level_execute_command(cmd=async_cmd))
# clean up after # clean up after
if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES: if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES:
@ -69,5 +76,3 @@ class ActionModule(ActionBase):
result['changed'] = True result['changed'] = True
return result return result

View file

@ -21,7 +21,6 @@ __metaclass__ = type
import json import json
import os import os
import pipes
import tempfile import tempfile
from ansible import constants as C from ansible import constants as C
@ -30,10 +29,15 @@ from ansible.utils.boolean import boolean
from ansible.utils.hashing import checksum from ansible.utils.hashing import checksum
from ansible.utils.unicode import to_bytes from ansible.utils.unicode import to_bytes
class ActionModule(ActionBase): class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' handler for file transfer operations ''' ''' handler for file transfer operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
source = self._task.args.get('src', None) source = self._task.args.get('src', None)
content = self._task.args.get('content', None) content = self._task.args.get('content', None)
@ -44,11 +48,17 @@ class ActionModule(ActionBase):
remote_src = boolean(self._task.args.get('remote_src', False)) remote_src = boolean(self._task.args.get('remote_src', False))
if (source is None and content is None and faf is None) or dest is None: if (source is None and content is None and faf is None) or dest is None:
return dict(failed=True, msg="src (or content) and dest are required") result['failed'] = True
result['msg'] = "src (or content) and dest are required"
return result
elif (source is not None or faf is not None) and content is not None: elif (source is not None or faf is not None) and content is not None:
return dict(failed=True, msg="src and content are mutually exclusive") result['failed'] = True
result['msg'] = "src and content are mutually exclusive"
return result
elif content is not None and dest is not None and dest.endswith("/"): elif content is not None and dest is not None and dest.endswith("/"):
return dict(failed=True, msg="dest must be a file if content is defined") result['failed'] = True
result['msg'] = "dest must be a file if content is defined"
return result
# Check if the source ends with a "/" # Check if the source ends with a "/"
source_trailing_slash = False source_trailing_slash = False
@ -69,19 +79,24 @@ class ActionModule(ActionBase):
content_tempfile = self._create_content_tempfile(content) content_tempfile = self._create_content_tempfile(content)
source = content_tempfile source = content_tempfile
except Exception as err: except Exception as err:
return dict(failed=True, msg="could not write content temp file: %s" % err) result['failed'] = True
result['msg'] = "could not write content temp file: %s" % err
return result
# if we have first_available_file in our vars # if we have first_available_file in our vars
# look up the files and use the first one we find as src # look up the files and use the first one we find as src
elif faf: elif faf:
source = self._get_first_available_file(faf, task_vars.get('_original_file', None)) source = self._get_first_available_file(faf, task_vars.get('_original_file', None))
if source is None: if source is None:
return dict(failed=True, msg="could not find src in first_available_file list") result['failed'] = True
result['msg'] = "could not find src in first_available_file list"
return result
elif remote_src: elif remote_src:
new_module_args = self._task.args.copy() new_module_args = self._task.args.copy()
del new_module_args['remote_src'] del new_module_args['remote_src']
return self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, delete_remote_tmp=False) result.update(self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, delete_remote_tmp=False))
return result
else: else:
if self._task._role is not None: if self._task._role is not None:
@ -117,7 +132,7 @@ class ActionModule(ActionBase):
source_files.append((source, os.path.basename(source))) source_files.append((source, os.path.basename(source)))
changed = False changed = False
module_result = {"changed": False} module_return = dict(changed=False)
# A register for if we executed a module. # A register for if we executed a module.
# Used to cut down on command calls when not recursive. # Used to cut down on command calls when not recursive.
@ -142,7 +157,9 @@ class ActionModule(ActionBase):
# If local_checksum is not defined we can't find the file so we should fail out. # If local_checksum is not defined we can't find the file so we should fail out.
if local_checksum is None: if local_checksum is None:
return dict(failed=True, msg="could not find src=%s" % source_full) result['failed'] = True
result['msg'] = "could not find src=%s" % source_full
return result
# This is kind of optimization - if user told us destination is # This is kind of optimization - if user told us destination is
# dir, do path manipulation right away, otherwise we still check # dir, do path manipulation right away, otherwise we still check
@ -160,7 +177,9 @@ class ActionModule(ActionBase):
if content is not None: if content is not None:
# If source was defined as content remove the temporary file and fail out. # If source was defined as content remove the temporary file and fail out.
self._remove_tempfile_if_content_defined(content, content_tempfile) self._remove_tempfile_if_content_defined(content, content_tempfile)
return dict(failed=True, msg="can not use content with a dir as dest") result['failed'] = True
result['msg'] = "can not use content with a dir as dest"
return result
else: else:
# Append the relative source location to the destination and retry remote_checksum # Append the relative source location to the destination and retry remote_checksum
dest_file = self._connection._shell.join_path(dest, source_rel) dest_file = self._connection._shell.join_path(dest, source_rel)
@ -250,9 +269,10 @@ class ActionModule(ActionBase):
if not module_return.get('checksum'): if not module_return.get('checksum'):
module_return['checksum'] = local_checksum module_return['checksum'] = local_checksum
if module_return.get('failed') == True: if module_return.get('failed'):
return module_return result.update(module_return)
if module_return.get('changed') == True: return result
if module_return.get('changed'):
changed = True changed = True
# the file module returns the file path as 'path', but # the file module returns the file path as 'path', but
@ -265,9 +285,9 @@ class ActionModule(ActionBase):
self._remove_tmp_path(tmp) self._remove_tmp_path(tmp)
if module_executed and len(source_files) == 1: if module_executed and len(source_files) == 1:
result = module_return result.update(module_return)
else: else:
result = dict(dest=dest, src=source, changed=changed) result.update(dict(dest=dest, src=source, changed=changed))
if diffs: if diffs:
result['diff'] = diffs result['diff'] = diffs
@ -288,8 +308,6 @@ class ActionModule(ActionBase):
f.close() f.close()
return content_tempfile return content_tempfile
def _remove_tempfile_if_content_defined(self, content, content_tempfile): def _remove_tempfile_if_content_defined(self, content, content_tempfile):
if content is not None: if content is not None:
os.remove(content_tempfile) os.remove(content_tempfile)

View file

@ -20,27 +20,32 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
class ActionModule(ActionBase): class ActionModule(ActionBase):
''' Print statements during execution ''' ''' Print statements during execution '''
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
if 'msg' in self._task.args: if 'msg' in self._task.args:
if 'fail' in self._task.args and boolean(self._task.args['fail']): if 'fail' in self._task.args and boolean(self._task.args['fail']):
result = dict(failed=True, msg=self._task.args['msg']) result['failed'] = True
result['msg'] = self._task.args['msg']
else: else:
result = dict(msg=self._task.args['msg']) result['msg'] = self._task.args['msg']
# FIXME: move the LOOKUP_REGEX somewhere else # FIXME: move the LOOKUP_REGEX somewhere else
elif 'var' in self._task.args: # and not utils.LOOKUP_REGEX.search(self._task.args['var']): elif 'var' in self._task.args: # and not utils.LOOKUP_REGEX.search(self._task.args['var']):
results = self._templar.template(self._task.args['var'], convert_bare=True) results = self._templar.template(self._task.args['var'], convert_bare=True)
if results == self._task.args['var']: if results == self._task.args['var']:
results = "VARIABLE IS NOT DEFINED!" results = "VARIABLE IS NOT DEFINED!"
result = dict()
result[self._task.args['var']] = results result[self._task.args['var']] = results
else: else:
result = dict(msg='here we are') result['msg'] = 'here we are'
# force flag to make debug output module always verbose # force flag to make debug output module always verbose
result['_ansible_verbose_always'] = True result['_ansible_verbose_always'] = True

View file

@ -20,16 +20,22 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
''' Fail with custom message ''' ''' Fail with custom message '''
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
msg = 'Failed as requested from task' msg = 'Failed as requested from task'
if self._task.args and 'msg' in self._task.args: if self._task.args and 'msg' in self._task.args:
msg = self._task.args.get('msg') msg = self._task.args.get('msg')
return dict(failed=True, msg=msg) result['failed'] = True
result['msg'] = msg
return result

View file

@ -26,13 +26,20 @@ from ansible.utils.boolean import boolean
from ansible.utils.hashing import checksum, checksum_s, md5, secure_hash from ansible.utils.hashing import checksum, checksum_s, md5, secure_hash
from ansible.utils.path import makedirs_safe from ansible.utils.path import makedirs_safe
class ActionModule(ActionBase): class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' handler for fetch operations ''' ''' handler for fetch operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
if self._play_context.check_mode: if self._play_context.check_mode:
return dict(skipped=True, msg='check mode not (yet) supported for this module') result['skipped'] = True
result['msg'] = 'check mode not (yet) supported for this module'
return result
source = self._task.args.get('src', None) source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None) dest = self._task.args.get('dest', None)
@ -41,10 +48,14 @@ class ActionModule(ActionBase):
validate_checksum = boolean(self._task.args.get('validate_checksum', self._task.args.get('validate_md5'))) validate_checksum = boolean(self._task.args.get('validate_checksum', self._task.args.get('validate_md5')))
if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args: if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args:
return dict(failed=True, msg="validate_checksum and validate_md5 cannot both be specified") result['failed'] = True
result['msg'] = "validate_checksum and validate_md5 cannot both be specified"
return result
if source is None or dest is None: if source is None or dest is None:
return dict(failed=True, msg="src and dest are required") result['failed'] = True
result['msg'] = "src and dest are required"
return result
source = self._connection._shell.join_path(source) source = self._connection._shell.join_path(source)
source = self._remote_expand_user(source) source = self._remote_expand_user(source)
@ -58,8 +69,12 @@ class ActionModule(ActionBase):
slurpres = self._execute_module(module_name='slurp', module_args=dict(src=source), task_vars=task_vars, tmp=tmp) slurpres = self._execute_module(module_name='slurp', module_args=dict(src=source), task_vars=task_vars, tmp=tmp)
if slurpres.get('failed'): if slurpres.get('failed'):
if remote_checksum == '1' and not fail_on_missing: if remote_checksum == '1' and not fail_on_missing:
return dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False) result['msg'] = "the remote file does not exist, not transferring, ignored"
return slurpres result['file'] = source
result['changed'] = False
return result
result.update(slurpres)
return result
else: else:
if slurpres['encoding'] == 'base64': if slurpres['encoding'] == 'base64':
remote_data = base64.b64decode(slurpres['content']) remote_data = base64.b64decode(slurpres['content'])
@ -103,18 +118,30 @@ class ActionModule(ActionBase):
# these don't fail because you may want to transfer a log file that possibly MAY exist # 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 # but keep going to fetch other log files
if remote_checksum == '0': if remote_checksum == '0':
result = dict(msg="unable to calculate the checksum of the remote file", file=source, changed=False) result['msg'] = "unable to calculate the checksum of the remote file"
result['file'] = source
result['changed'] = False
elif remote_checksum == '1': elif remote_checksum == '1':
if fail_on_missing: if fail_on_missing:
result = dict(failed=True, msg="the remote file does not exist", file=source) result['failed'] = True
result['msg'] = "the remote file does not exist"
result['file'] = source
else: else:
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False) result['msg'] = "the remote file does not exist, not transferring, ignored"
result['file'] = source
result['changed'] = False
elif remote_checksum == '2': elif remote_checksum == '2':
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False) result['msg'] = "no read permission on remote file, not transferring, ignored"
result['file'] = source
result['changed'] = False
elif remote_checksum == '3': elif remote_checksum == '3':
result = dict(msg="remote file is a directory, fetch cannot work on directories", file=source, changed=False) result['msg'] = "remote file is a directory, fetch cannot work on directories"
result['file'] = source
result['changed'] = False
elif remote_checksum == '4': elif remote_checksum == '4':
result = dict(msg="python isn't present on the system. Unable to compute checksum", file=source, changed=False) result['msg'] = "python isn't present on the system. Unable to compute checksum"
result['file'] = source
result['changed'] = False
return result return result
# calculate checksum for the local file # calculate checksum for the local file
@ -143,8 +170,10 @@ class ActionModule(ActionBase):
new_md5 = None new_md5 = None
if validate_checksum and new_checksum != remote_checksum: if validate_checksum and new_checksum != remote_checksum:
return dict(failed=True, md5sum=new_md5, msg="checksum mismatch", file=source, dest=dest, remote_md5sum=None, checksum=new_checksum, remote_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))
return dict(changed=True, md5sum=new_md5, dest=dest, remote_md5sum=None, checksum=new_checksum, remote_checksum=remote_checksum) return result
result.update(dict(changed=True, md5sum=new_md5, dest=dest, remote_md5sum=None, checksum=new_checksum, remote_checksum=remote_checksum))
return result
else: else:
# For backwards compatibility. We'll return None on FIPS enabled # For backwards compatibility. We'll return None on FIPS enabled
# systems # systems
@ -153,5 +182,5 @@ class ActionModule(ActionBase):
except ValueError: except ValueError:
local_md5 = None local_md5 = None
return dict(changed=False, md5sum=local_md5, file=source, dest=dest, checksum=local_checksum) result.update(dict(changed=False, md5sum=local_md5, file=source, dest=dest, checksum=local_checksum))
return result

View file

@ -19,19 +19,27 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
''' Create inventory groups based on variables ''' ''' Create inventory groups based on variables '''
### We need to be able to modify the inventory ### We need to be able to modify the inventory
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
if not 'key' in self._task.args: result = super(ActionModule, self).run(tmp, task_vars)
return dict(failed=True, msg="the 'key' param is required when using group_by")
if 'key' not in self._task.args:
result['failed'] = True
result['msg'] = "the 'key' param is required when using group_by"
return result
group_name = self._task.args.get('key') group_name = self._task.args.get('key')
group_name = group_name.replace(' ','-') group_name = group_name.replace(' ','-')
return dict(changed=True, add_group=group_name) result['changed'] = True
result['add_group'] = group_name
return result

View file

@ -20,14 +20,18 @@ __metaclass__ = type
import os import os
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.parsing import DataLoader
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
source = self._task.args.get('_raw_params') source = self._task.args.get('_raw_params')
@ -43,7 +47,11 @@ class ActionModule(ActionBase):
data = {} data = {}
if not isinstance(data, dict): if not isinstance(data, dict):
raise AnsibleError("%s must be stored as a dictionary/hash" % source) raise AnsibleError("%s must be stored as a dictionary/hash" % source)
return dict(ansible_facts=data, _ansible_no_log=not show_content) result['ansible_facts'] = data
result['_ansible_no_log'] = not show_content
else: else:
return dict(failed=True, msg="Source file not found.", file=source) result['failed'] = True
result['msg'] = "Source file not found."
result['file'] = source
return result

View file

@ -19,11 +19,15 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
results = self._execute_module(tmp=tmp, task_vars=task_vars) results = super(ActionModule, self).run(tmp, task_vars)
results.update(self._execute_module(tmp=tmp, task_vars=task_vars))
# Remove special fields from the result, which can only be set # Remove special fields from the result, which can only be set
# internally by the executor engine. We do this only here in # internally by the executor engine. We do this only here in

View file

@ -17,18 +17,20 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' handler for package operations ''' ''' handler for package operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
name = self._task.args.get('name', None)
state = self._task.args.get('state', None)
module = self._task.args.get('use', 'auto') module = self._task.args.get('use', 'auto')
if module == 'auto': if module == 'auto':
@ -40,13 +42,15 @@ class ActionModule(ActionBase):
if module == 'auto': if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_pkg_mgr'), task_vars=task_vars) facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_pkg_mgr'), task_vars=task_vars)
self._display.debug("Facts %s" % facts) self._display.debug("Facts %s" % facts)
if not 'failed' in facts: if 'failed' not in facts:
module = getattr(facts['ansible_facts'], 'ansible_pkg_mgr', 'auto') module = getattr(facts['ansible_facts'], 'ansible_pkg_mgr', 'auto')
if module != 'auto': if module != 'auto':
if module not in self._shared_loader_obj.module_loader: if module not in self._shared_loader_obj.module_loader:
return {'failed': True, 'msg': 'Could not find a module for %s.' % module} result['failed'] = True
result['msg'] = 'Could not find a module for %s.' % module
return result
# run the 'package' module # run the 'package' module
new_module_args = self._task.args.copy() new_module_args = self._task.args.copy()
@ -54,8 +58,9 @@ class ActionModule(ActionBase):
del new_module_args['use'] del new_module_args['use']
self._display.vvvv("Running %s" % module) self._display.vvvv("Running %s" % module)
return self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars) result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars))
return result
else: else:
result['failed'] = True
return {'failed': True, 'msg': 'Could not detect which package manager to use. Try gathering facts or setting the "use" option.'} result['msg'] = 'Could not detect which package manager to use. Try gathering facts or setting the "use" option.'
return result

View file

@ -23,20 +23,27 @@ import os
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
class ActionModule(ActionBase): class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
src = self._task.args.get('src', None) src = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
remote_src = boolean(self._task.args.get('remote_src', 'no')) remote_src = boolean(self._task.args.get('remote_src', 'no'))
if src is None: if src is None:
return dict(failed=True, msg="src is required") result['failed'] = True
result['msg'] = "src is required"
return result
elif remote_src: elif remote_src:
# everything is remote, so we just execute the module # everything is remote, so we just execute the module
# without changing any of the module arguments # without changing any of the module arguments
return self._execute_module(task_vars=task_vars) result.update(self._execute_module(task_vars=task_vars))
return result
if self._task._role is not None: if self._task._role is not None:
src = self._loader.path_dwim_relative(self._task._role._role_path, 'files', src) src = self._loader.path_dwim_relative(self._task._role._role_path, 'files', src)
@ -61,4 +68,5 @@ class ActionModule(ActionBase):
) )
) )
return self._execute_module('patch', module_args=new_module_args, task_vars=task_vars) result.update(self._execute_module('patch', module_args=new_module_args, task_vars=task_vars))
return result

View file

@ -27,25 +27,32 @@ from os import isatty
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class AnsibleTimeoutExceeded(Exception): class AnsibleTimeoutExceeded(Exception):
pass pass
def timeout_handler(signum, frame): def timeout_handler(signum, frame):
raise AnsibleTimeoutExceeded raise AnsibleTimeoutExceeded
class ActionModule(ActionBase): class ActionModule(ActionBase):
''' pauses execution for a length or time, or until input is received ''' ''' pauses execution for a length or time, or until input is received '''
PAUSE_TYPES = ['seconds', 'minutes', 'prompt', ''] PAUSE_TYPES = ['seconds', 'minutes', 'prompt', '']
BYPASS_HOST_LOOP = True BYPASS_HOST_LOOP = True
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' run the pause action module ''' ''' run the pause action module '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
duration_unit = 'minutes' duration_unit = 'minutes'
prompt = None prompt = None
seconds = None seconds = None
result = dict( result.update(dict(
changed = False, changed = False,
rc = 0, rc = 0,
stderr = '', stderr = '',
@ -53,37 +60,37 @@ class ActionModule(ActionBase):
start = None, start = None,
stop = None, stop = None,
delta = None, delta = None,
) ))
# Is 'args' empty, then this is the default prompted pause # Is 'args' empty, then this is the default prompted pause
if self._task.args is None or len(self._task.args.keys()) == 0: if self._task.args is None or len(self._task.args.keys()) == 0:
pause_type = 'prompt'
prompt = "[%s]\nPress enter to continue:" % self._task.get_name().strip() prompt = "[%s]\nPress enter to continue:" % self._task.get_name().strip()
# Are 'minutes' or 'seconds' keys that exist in 'args'? # Are 'minutes' or 'seconds' keys that exist in 'args'?
elif 'minutes' in self._task.args or 'seconds' in self._task.args: elif 'minutes' in self._task.args or 'seconds' in self._task.args:
try: try:
if 'minutes' in self._task.args: if 'minutes' in self._task.args:
pause_type = 'minutes'
# The time() command operates in seconds so we need to # The time() command operates in seconds so we need to
# recalculate for minutes=X values. # recalculate for minutes=X values.
seconds = int(self._task.args['minutes']) * 60 seconds = int(self._task.args['minutes']) * 60
else: else:
pause_type = 'seconds'
seconds = int(self._task.args['seconds']) seconds = int(self._task.args['seconds'])
duration_unit = 'seconds' duration_unit = 'seconds'
except ValueError as e: except ValueError as e:
return dict(failed=True, msg="non-integer value given for prompt duration:\n%s" % str(e)) result['failed'] = True
result['msg'] = "non-integer value given for prompt duration:\n%s" % str(e)
return result
# Is 'prompt' a key in 'args'? # Is 'prompt' a key in 'args'?
elif 'prompt' in self._task.args: elif 'prompt' in self._task.args:
pause_type = 'prompt'
prompt = "[%s]\n%s:" % (self._task.get_name().strip(), self._task.args['prompt']) prompt = "[%s]\n%s:" % (self._task.get_name().strip(), self._task.args['prompt'])
else: else:
# I have no idea what you're trying to do. But it's so wrong. # I have no idea what you're trying to do. But it's so wrong.
return dict(failed=True, msg="invalid pause type given. must be one of: %s" % ", ".join(self.PAUSE_TYPES)) result['failed'] = True
result['msg'] = "invalid pause type given. must be one of: %s" % ", ".join(self.PAUSE_TYPES)
return result
######################################################################## ########################################################################
# Begin the hard work! # Begin the hard work!

View file

@ -21,17 +21,23 @@ from ansible.plugins.action import ActionBase
import re import re
class ActionModule(ActionBase): class ActionModule(ActionBase):
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
if self._play_context.check_mode: if self._play_context.check_mode:
# in --check mode, always skip this module execution # in --check mode, always skip this module execution
return dict(skipped=True) result['skipped'] = True
return result
executable = self._task.args.get('executable') executable = self._task.args.get('executable')
result = self._low_level_execute_command(self._task.args.get('_raw_params'), executable=executable) result.update(self._low_level_execute_command(self._task.args.get('_raw_params'), executable=executable))
# for some modules (script, raw), the sudo success key # for some modules (script, raw), the sudo success key
# may leak into the stdout due to the way the sudo/su # may leak into the stdout due to the way the sudo/su

View file

@ -22,14 +22,21 @@ import os
from ansible import constants as C from ansible import constants as C
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
class ActionModule(ActionBase): class ActionModule(ActionBase):
TRANSFERS_FILES = True TRANSFERS_FILES = True
def run(self, tmp=None, task_vars=None): def run(self, tmp=None, task_vars=None):
''' handler for file transfer operations ''' ''' handler for file transfer operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
if self._play_context.check_mode: if self._play_context.check_mode:
return dict(skipped=True, msg='check mode not supported for this module') result['skipped'] = True
result['msg'] = 'check mode not supported for this module'
return result
if not tmp: if not tmp:
tmp = self._make_tmp_path() tmp = self._make_tmp_path()
@ -39,8 +46,8 @@ class ActionModule(ActionBase):
# do not run the command if the line contains creates=filename # do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence # and the filename already exists. This allows idempotence
# of command executions. # of command executions.
result = self._execute_module(module_name='stat', module_args=dict(path=creates), task_vars=task_vars, tmp=tmp, persist_files=True) res = self._execute_module(module_name='stat', module_args=dict(path=creates), task_vars=task_vars, tmp=tmp, persist_files=True)
stat = result.get('stat', None) stat = res.get('stat', None)
if stat and stat.get('exists', False): if stat and stat.get('exists', False):
return dict(skipped=True, msg=("skipped, since %s exists" % creates)) return dict(skipped=True, msg=("skipped, since %s exists" % creates))
@ -49,8 +56,8 @@ class ActionModule(ActionBase):
# do not run the command if the line contains removes=filename # do not run the command if the line contains removes=filename
# and the filename does not exist. This allows idempotence # and the filename does not exist. This allows idempotence
# of command executions. # of command executions.
result = self._execute_module(module_name='stat', module_args=dict(path=removes), task_vars=task_vars, tmp=tmp, persist_files=True) res = self._execute_module(module_name='stat', module_args=dict(path=removes), task_vars=task_vars, tmp=tmp, persist_files=True)
stat = result.get('stat', None) stat = res.get('stat', None)
if stat and not stat.get('exists', False): if stat and not stat.get('exists', False):
return dict(skipped=True, msg=("skipped, since %s does not exist" % removes)) return dict(skipped=True, msg=("skipped, since %s does not exist" % removes))
@ -84,7 +91,7 @@ class ActionModule(ActionBase):
env_string = self._compute_environment_string() env_string = self._compute_environment_string()
script_cmd = ' '.join([env_string, tmp_src, args]) script_cmd = ' '.join([env_string, tmp_src, args])
result = self._low_level_execute_command(cmd=script_cmd, sudoable=True) result.update(self._low_level_execute_command(cmd=script_cmd, sudoable=True))
# clean up after # clean up after
if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES: if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES:

View file

@ -25,11 +25,13 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' handler for package operations ''' ''' handler for package operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
name = self._task.args.get('name', None)
state = self._task.args.get('state', None)
module = self._task.args.get('use', 'auto') module = self._task.args.get('use', 'auto')
if module == 'auto': if module == 'auto':
@ -41,7 +43,7 @@ class ActionModule(ActionBase):
if module == 'auto': if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_service_mgr'), task_vars=task_vars) facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_service_mgr'), task_vars=task_vars)
self._display.debug("Facts %s" % facts) self._display.debug("Facts %s" % facts)
if not 'failed' in facts: if 'failed' not in facts:
module = getattr(facts['ansible_facts'], 'ansible_service_mgr', 'auto') module = getattr(facts['ansible_facts'], 'ansible_service_mgr', 'auto')
if not module or module == 'auto' or module not in self._shared_loader_obj.module_loader: if not module or module == 'auto' or module not in self._shared_loader_obj.module_loader:
@ -54,8 +56,9 @@ class ActionModule(ActionBase):
del new_module_args['use'] del new_module_args['use']
self._display.vvvv("Running %s" % module) self._display.vvvv("Running %s" % module)
return self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars) result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars))
else: else:
result['failed'] = True
result['msg'] = 'Could not detect which service manager to use. Try gathering facts or setting the "use" option.'
return {'failed': True, 'msg': 'Could not detect which service manager to use. Try gathering facts or setting the "use" option.'} return result

View file

@ -20,7 +20,6 @@ __metaclass__ = type
from ansible.compat.six import iteritems from ansible.compat.six import iteritems
from ansible.errors import AnsibleError
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
from ansible.utils.vars import isidentifier from ansible.utils.vars import isidentifier
@ -30,16 +29,26 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = False TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
facts = dict() facts = dict()
if self._task.args: if self._task.args:
for (k, v) in iteritems(self._task.args): for (k, v) in iteritems(self._task.args):
k = self._templar.template(k) k = self._templar.template(k)
if not isidentifier(k): if not isidentifier(k):
return dict(failed=True, msg="The variable name '%s' is not valid. Variables must start with a letter or underscore character, and contain only letters, numbers and underscores." % k) result['failed'] = True
result['msg'] = "The variable name '%s' is not valid. Variables must start with a letter or underscore character, and contain only letters, numbers and underscores." % k
return result
if isinstance(v, basestring) and v.lower() in ('true', 'false', 'yes', 'no'): if isinstance(v, basestring) and v.lower() in ('true', 'false', 'yes', 'no'):
v = boolean(v) v = boolean(v)
facts[k] = v facts[k] = v
return dict(changed=False, ansible_facts=facts)
result['changed'] = False
result['ansible_facts'] = facts
return result

View file

@ -101,8 +101,12 @@ class ActionModule(ActionBase):
if key.startswith("ansible_") and key.endswith("_interpreter"): if key.startswith("ansible_") and key.endswith("_interpreter"):
task_vars[key] = localhost[key] task_vars[key] = localhost[key]
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' generates params and passes them on to the rsync module ''' ''' generates params and passes them on to the rsync module '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
original_transport = task_vars.get('ansible_connection') or self._play_context.connection original_transport = task_vars.get('ansible_connection') or self._play_context.connection
remote_transport = False remote_transport = False
@ -124,7 +128,7 @@ class ActionModule(ActionBase):
# ansible's delegate_to mechanism to determine which host rsync is # ansible's delegate_to mechanism to determine which host rsync is
# running on so localhost could be a non-controller machine if # running on so localhost could be a non-controller machine if
# delegate_to is used) # delegate_to is used)
src_host = '127.0.0.1' src_host = '127.0.0.1'
inventory_hostname = task_vars.get('inventory_hostname') inventory_hostname = task_vars.get('inventory_hostname')
dest_host_inventory_vars = task_vars['hostvars'].get(inventory_hostname) dest_host_inventory_vars = task_vars['hostvars'].get(inventory_hostname)
dest_host = dest_host_inventory_vars.get('ansible_ssh_host', inventory_hostname) dest_host = dest_host_inventory_vars.get('ansible_ssh_host', inventory_hostname)
@ -236,7 +240,7 @@ class ActionModule(ActionBase):
self._task.args['ssh_args'] = C.ANSIBLE_SSH_ARGS self._task.args['ssh_args'] = C.ANSIBLE_SSH_ARGS
# run the module and store the result # run the module and store the result
result = self._execute_module('synchronize', task_vars=task_vars) result.update(self._execute_module('synchronize', task_vars=task_vars))
if 'SyntaxError' in result['msg']: if 'SyntaxError' in result['msg']:
# Emit a warning about using python3 because synchronize is # Emit a warning about using python3 because synchronize is

View file

@ -17,7 +17,6 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import base64
import datetime import datetime
import os import os
import pwd import pwd
@ -29,6 +28,7 @@ from ansible.utils.hashing import checksum_s
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
from ansible.utils.unicode import to_bytes, to_unicode from ansible.utils.unicode import to_bytes, to_unicode
class ActionModule(ActionBase): class ActionModule(ActionBase):
TRANSFERS_FILES = True TRANSFERS_FILES = True
@ -52,8 +52,12 @@ class ActionModule(ActionBase):
return remote_checksum return remote_checksum
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' handler for template operations ''' ''' handler for template operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
source = self._task.args.get('src', None) source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None) dest = self._task.args.get('dest', None)
@ -61,7 +65,9 @@ class ActionModule(ActionBase):
force = boolean(self._task.args.get('force', False)) force = boolean(self._task.args.get('force', False))
if (source is None and faf is not None) or dest is None: if (source is None and faf is not None) or dest is None:
return dict(failed=True, msg="src and dest are required") result['failed'] = True
result['msg'] = "src and dest are required"
return result
if tmp is None: if tmp is None:
tmp = self._make_tmp_path() tmp = self._make_tmp_path()
@ -69,7 +75,9 @@ class ActionModule(ActionBase):
if faf: if faf:
source = self._get_first_available_file(faf, task_vars.get('_original_file', None, 'templates')) source = self._get_first_available_file(faf, task_vars.get('_original_file', None, 'templates'))
if source is None: if source is None:
return dict(failed=True, msg="could not find src in first_available_file list") result['failed'] = True
result['msg'] = "could not find src in first_available_file list"
return result
else: else:
if self._task._role is not None: if self._task._role is not None:
source = self._loader.path_dwim_relative(self._task._role._role_path, 'templates', source) source = self._loader.path_dwim_relative(self._task._role._role_path, 'templates', source)
@ -128,20 +136,21 @@ class ActionModule(ActionBase):
resultant = self._templar.template(template_data, preserve_trailing_newlines=True, escape_backslashes=False, convert_data=False) resultant = self._templar.template(template_data, preserve_trailing_newlines=True, escape_backslashes=False, convert_data=False)
self._templar.set_available_variables(old_vars) self._templar.set_available_variables(old_vars)
except Exception as e: except Exception as e:
return dict(failed=True, msg=type(e).__name__ + ": " + str(e)) result['failed'] = True
result['msg'] = type(e).__name__ + ": " + str(e)
return result
local_checksum = checksum_s(resultant) local_checksum = checksum_s(resultant)
remote_checksum = self.get_checksum(dest, task_vars, not directory_prepended, source=source) remote_checksum = self.get_checksum(dest, task_vars, not directory_prepended, source=source)
if isinstance(remote_checksum, dict): if isinstance(remote_checksum, dict):
# Error from remote_checksum is a dict. Valid return is a str # Error from remote_checksum is a dict. Valid return is a str
return remote_checksum result.update(remote_checksum)
return result
diff = {} diff = {}
new_module_args = self._task.args.copy() new_module_args = self._task.args.copy()
if local_checksum != remote_checksum: if local_checksum != remote_checksum:
dest_contents = ''
# if showing diffs, we need to get the remote value # if showing diffs, we need to get the remote value
if self._play_context.diff: if self._play_context.diff:
diff = self._get_diff_data(dest, resultant, task_vars, source_file=False) diff = self._get_diff_data(dest, resultant, task_vars, source_file=False)
@ -162,12 +171,12 @@ class ActionModule(ActionBase):
follow=True, follow=True,
), ),
) )
result = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars) result.update(self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars))
else: else:
if remote_checksum == '1' or force: if remote_checksum == '1' or force:
result = dict(changed=True) result['changed'] = True
else: else:
result = dict(changed=False) result['changed'] = False
if result.get('changed', False) and self._play_context.diff: if result.get('changed', False) and self._play_context.diff:
result['diff'] = diff result['diff'] = diff
@ -189,5 +198,5 @@ class ActionModule(ActionBase):
), ),
) )
return self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars) result.update(self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars))
return result

View file

@ -19,7 +19,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import os import os
import pipes
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
@ -29,8 +28,12 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = True TRANSFERS_FILES = True
def run(self, tmp=None, task_vars=dict()): def run(self, tmp=None, task_vars=None):
''' handler for unarchive operations ''' ''' handler for unarchive operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
source = self._task.args.get('src', None) source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None) dest = self._task.args.get('dest', None)
@ -38,7 +41,9 @@ class ActionModule(ActionBase):
creates = self._task.args.get('creates', None) creates = self._task.args.get('creates', None)
if source is None or dest is None: if source is None or dest is None:
return dict(failed=True, msg="src (or content) and dest are required") result['failed'] = True
result['msg'] = "src (or content) and dest are required"
return result
if not tmp: if not tmp:
tmp = self._make_tmp_path() tmp = self._make_tmp_path()
@ -47,11 +52,12 @@ class ActionModule(ActionBase):
# do not run the command if the line contains creates=filename # do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence # and the filename already exists. This allows idempotence
# of command executions. # of command executions.
module_args_tmp = "path=%s" % creates
result = self._execute_module(module_name='stat', module_args=dict(path=creates), task_vars=task_vars) result = self._execute_module(module_name='stat', module_args=dict(path=creates), task_vars=task_vars)
stat = result.get('stat', None) stat = result.get('stat', None)
if stat and stat.get('exists', False): if stat and stat.get('exists', False):
return dict(skipped=True, msg=("skipped, since %s exists" % creates)) result['skipped'] = True
result['msg'] = "skipped, since %s exists" % creates
return result
dest = self._remote_expand_user(dest) # CCTODO: Fix path for Windows hosts. dest = self._remote_expand_user(dest) # CCTODO: Fix path for Windows hosts.
source = os.path.expanduser(source) source = os.path.expanduser(source)
@ -68,9 +74,13 @@ class ActionModule(ActionBase):
remote_checksum = self._remote_checksum(dest, all_vars=task_vars) remote_checksum = self._remote_checksum(dest, all_vars=task_vars)
if remote_checksum != '3': if remote_checksum != '3':
return dict(failed=True, msg="dest '%s' must be an existing dir" % dest) result['failed'] = True
result['msg'] = "dest '%s' must be an existing dir" % dest
return result
elif remote_checksum == '4': elif remote_checksum == '4':
return dict(failed=True, msg="python isn't present on the system. Unable to compute checksum") result['failed'] = True
result['msg'] = "python isn't present on the system. Unable to compute checksum"
return result
if copy: if copy:
# transfer the file to a remote tmp location # transfer the file to a remote tmp location
@ -103,5 +113,5 @@ class ActionModule(ActionBase):
) )
# execute the unarchive module now, with the updated args # execute the unarchive module now, with the updated args
return self._execute_module(module_args=new_module_args, task_vars=task_vars) result.update(self._execute_module(module_args=new_module_args, task_vars=task_vars))
return result

View file

@ -22,6 +22,7 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.plugins.action.copy import ActionModule as CopyActionModule from ansible.plugins.action.copy import ActionModule as CopyActionModule
# Even though CopyActionModule inherits from ActionBase, we still need to # Even though CopyActionModule inherits from ActionBase, we still need to
# directly inherit from ActionBase to appease the plugin loader. # directly inherit from ActionBase to appease the plugin loader.
class ActionModule(CopyActionModule, ActionBase): class ActionModule(CopyActionModule, ActionBase):

View file

@ -22,6 +22,7 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.plugins.action.template import ActionModule as TemplateActionModule from ansible.plugins.action.template import ActionModule as TemplateActionModule
# Even though TemplateActionModule inherits from ActionBase, we still need to # Even though TemplateActionModule inherits from ActionBase, we still need to
# directly inherit from ActionBase to appease the plugin loader. # directly inherit from ActionBase to appease the plugin loader.
class ActionModule(TemplateActionModule, ActionBase): class ActionModule(TemplateActionModule, ActionBase):

View file

@ -61,20 +61,26 @@ class CallbackModule(CallbackBase):
if result._task.action in C.MODULE_NO_JSON: if result._task.action in C.MODULE_NO_JSON:
self._display.display(self._command_generic_msg(result._host.get_name(), result._result,"FAILED"), color='red') self._display.display(self._command_generic_msg(result._host.get_name(), result._result,"FAILED"), color='red')
else: else:
self._display.display("%s | FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result, indent=4)), color='red') abridged_result = result.copy(result._result)
abridged_result.pop('invocation', None)
self._display.display("%s | FAILED! => %s" % (result._host.get_name(), self._dump_results(abridged_result, indent=4)), color='red')
def v2_runner_on_ok(self, result): def v2_runner_on_ok(self, result):
if result._task.action in C.MODULE_NO_JSON: if result._task.action in C.MODULE_NO_JSON:
self._display.display(self._command_generic_msg(result._host.get_name(), result._result,"SUCCESS"), color='green') self._display.display(self._command_generic_msg(result._host.get_name(), result._result,"SUCCESS"), color='green')
else: else:
self._display.display("%s | SUCCESS => %s" % (result._host.get_name(), self._dump_results(result._result, indent=4)), color='green') abridged_result = result.copy(result._result)
abridged_result.pop('invocation', None)
self._display.display("%s | SUCCESS => %s" % (result._host.get_name(), self._dump_results(abridged_result, indent=4)), color='green')
self._handle_warnings(result._result) self._handle_warnings(result._result)
def v2_runner_on_skipped(self, result): def v2_runner_on_skipped(self, result):
self._display.display("%s | SKIPPED" % (result._host.get_name()), color='cyan') self._display.display("%s | SKIPPED" % (result._host.get_name()), color='cyan')
def v2_runner_on_unreachable(self, result): def v2_runner_on_unreachable(self, result):
self._display.display("%s | UNREACHABLE! => %s" % (result._host.get_name(), self._dump_results(result._result, indent=4)), color='yellow') abridged_result = result.copy(result._result)
abridged_result.pop('invocation', None)
self._display.display("%s | UNREACHABLE! => %s" % (result._host.get_name(), self._dump_results(abridged_result, indent=4)), color='yellow')
def v2_on_file_diff(self, result): def v2_on_file_diff(self, result):
if 'diff' in result._result and result._result['diff']: if 'diff' in result._result and result._result['diff']:

View file

@ -155,11 +155,9 @@
that: that:
- complex_param == "this is a param in a complex arg with double quotes" - complex_param == "this is a param in a complex arg with double quotes"
#- name: test variable module name - name: test variable module name
# action: "{{ variable_module_name }} msg='this should be debugged'" action: "{{ variable_module_name }} msg='this should be debugged'"
# register: result register: result
#
#- debug: var=result
- name: assert the task with variable module name ran - name: assert the task with variable module name ran
assert: assert:

View file

@ -29,9 +29,15 @@ from ansible.plugins.action import ActionBase
class TestActionBase(unittest.TestCase): class TestActionBase(unittest.TestCase):
class DerivedActionBase(ActionBase):
def run(self, tmp=None, task_vars=None):
# We're not testing the plugin run() method, just the helper
# methods ActionBase defines
return dict()
def test_sudo_only_if_user_differs(self): def test_sudo_only_if_user_differs(self):
play_context = PlayContext() play_context = PlayContext()
action_base = ActionBase(None, None, play_context, None, None, None) action_base = self.DerivedActionBase(None, None, play_context, None, None, None)
action_base._connection = Mock(exec_command=Mock(return_value=(0, '', ''))) action_base._connection = Mock(exec_command=Mock(return_value=(0, '', '')))
play_context.become = True play_context.become = True