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 tempfile
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.errors import AnsibleError, AnsibleConnectionFailure
@ -42,7 +43,7 @@ except ImportError:
from ansible.utils.display import Display
display = Display()
class ActionBase:
class ActionBase(with_metaclass(ABCMeta, object)):
'''
This class is the base class for all action plugins, and defines
@ -62,11 +63,39 @@ class ActionBase:
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
modify_module() function.
'''
if task_vars is None:
task_vars = dict()
# Search module path(s) for named module.
for mod_type in self._connection.module_implementation_preferences:
@ -329,10 +358,12 @@ class ActionBase:
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.
'''
if task_vars is None:
task_vars = dict()
# if a module name was not specified for this execution, use
# the action from the task
@ -441,13 +472,6 @@ class ActionBase:
if 'stdout' in data and 'stdout_lines' not in data:
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))
return data

View file

@ -20,27 +20,32 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
from ansible.plugins.action import ActionBase
from ansible.parsing.utils.addresses import parse_address
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.errors import AnsibleError
class ActionModule(ActionBase):
''' 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
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:
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
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)
if not name:
@ -48,7 +53,7 @@ class ActionModule(ActionBase):
if 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
new_groups = []
if groups:
@ -58,8 +63,11 @@ class ActionModule(ActionBase):
# Add any variables to the new_host
host_vars = dict()
special_args = frozenset(('name', 'hostname', 'groupname', 'groups'))
for k in self._task.args.keys():
if not k in [ 'name', 'hostname', 'groupname', 'groups' ]:
host_vars[k] = self._task.args[k]
if k not in special_args:
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.path
import pipes
import shutil
import tempfile
import base64
import re
from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean
from ansible.utils.hashing import checksum_s
class ActionModule(ActionBase):
TRANSFERS_FILES = True
@ -75,10 +73,16 @@ class ActionModule(ActionBase):
tmp.close()
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:
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)
dest = self._task.args.get('dest', None)
@ -87,12 +91,15 @@ class ActionModule(ActionBase):
regexp = self._task.args.get('regexp', None)
ignore_hidden = self._task.args.get('ignore_hidden', False)
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):
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:
src = self._loader.path_dwim_relative(self._task._role._role_path, 'files', src)
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)
if diff:
res['diff'] = diff
return res
result.update(res)
return result
else:
new_module_args = self._task.args.copy()
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.plugins.action import ActionBase
class ActionModule(ActionBase):
''' Fail with custom message '''
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')
msg = None
@ -38,7 +43,7 @@ class ActionModule(ActionBase):
# make sure the 'that' items are a list
thats = self._task.args['that']
if not isinstance(thats, list):
thats = [ thats ]
thats = [thats]
# Now we iterate over the that items, temporarily assigning them
# to the task's when value so we can evaluate the conditional using
@ -47,19 +52,18 @@ class ActionModule(ActionBase):
# that value now
cond = Conditional(loader=self._loader)
for that in thats:
cond.when = [ that ]
cond.when = [that]
test_result = cond.evaluate_conditional(templar=self._templar, all_vars=task_vars)
if not test_result:
result = dict(
failed = True,
evaluated_to = test_result,
assertion = that,
)
result['failed'] = True
result['evaluated_to'] = test_result
result['assertion'] = that
if msg:
result['msg'] = msg
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.plugins.action import 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 '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
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:
tmp = self._make_tmp_path()
@ -60,7 +67,7 @@ class ActionModule(ActionBase):
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]])
result = self._low_level_execute_command(cmd=async_cmd)
result.update(self._low_level_execute_command(cmd=async_cmd))
# clean up after
if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES:
@ -69,5 +76,3 @@ class ActionModule(ActionBase):
result['changed'] = True
return result

View file

@ -21,7 +21,6 @@ __metaclass__ = type
import json
import os
import pipes
import tempfile
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.unicode import to_bytes
class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()):
def run(self, tmp=None, task_vars=None):
''' 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)
content = self._task.args.get('content', None)
@ -44,11 +48,17 @@ class ActionModule(ActionBase):
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:
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:
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("/"):
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 "/"
source_trailing_slash = False
@ -69,19 +79,24 @@ class ActionModule(ActionBase):
content_tempfile = self._create_content_tempfile(content)
source = content_tempfile
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
# look up the files and use the first one we find as src
elif faf:
source = self._get_first_available_file(faf, task_vars.get('_original_file', 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:
new_module_args = self._task.args.copy()
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:
if self._task._role is not None:
@ -117,7 +132,7 @@ class ActionModule(ActionBase):
source_files.append((source, os.path.basename(source)))
changed = False
module_result = {"changed": False}
module_return = dict(changed=False)
# A register for if we executed a module.
# 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 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
# dir, do path manipulation right away, otherwise we still check
@ -160,7 +177,9 @@ 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)
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:
# Append the relative source location to the destination and retry remote_checksum
dest_file = self._connection._shell.join_path(dest, source_rel)
@ -250,9 +269,10 @@ class ActionModule(ActionBase):
if not module_return.get('checksum'):
module_return['checksum'] = local_checksum
if module_return.get('failed') == True:
return module_return
if module_return.get('changed') == True:
if module_return.get('failed'):
result.update(module_return)
return result
if module_return.get('changed'):
changed = True
# the file module returns the file path as 'path', but
@ -265,9 +285,9 @@ class ActionModule(ActionBase):
self._remove_tmp_path(tmp)
if module_executed and len(source_files) == 1:
result = module_return
result.update(module_return)
else:
result = dict(dest=dest, src=source, changed=changed)
result.update(dict(dest=dest, src=source, changed=changed))
if diffs:
result['diff'] = diffs
@ -288,8 +308,6 @@ class ActionModule(ActionBase):
f.close()
return content_tempfile
def _remove_tempfile_if_content_defined(self, content, content_tempfile):
if content is not None:
os.remove(content_tempfile)

View file

@ -20,27 +20,32 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean
class ActionModule(ActionBase):
''' Print statements during execution '''
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 '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:
result = dict(msg=self._task.args['msg'])
result['msg'] = self._task.args['msg']
# FIXME: move the LOOKUP_REGEX somewhere else
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)
if results == self._task.args['var']:
results = "VARIABLE IS NOT DEFINED!"
result = dict()
result[self._task.args['var']] = results
else:
result = dict(msg='here we are')
result['msg'] = 'here we are'
# force flag to make debug output module always verbose
result['_ansible_verbose_always'] = True

View file

@ -20,16 +20,22 @@ __metaclass__ = type
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
''' Fail with custom message '''
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'
if self._task.args and 'msg' in self._task.args:
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.path import makedirs_safe
class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()):
def run(self, tmp=None, task_vars=None):
''' 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:
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)
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')))
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:
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._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)
if slurpres.get('failed'):
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)
return slurpres
result['msg'] = "the remote file does not exist, not transferring, ignored"
result['file'] = source
result['changed'] = False
return result
result.update(slurpres)
return result
else:
if slurpres['encoding'] == 'base64':
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
# but keep going to fetch other log files
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':
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:
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':
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':
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':
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
# calculate checksum for the local file
@ -143,8 +170,10 @@ class ActionModule(ActionBase):
new_md5 = None
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)
return dict(changed=True, md5sum=new_md5, 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 result
result.update(dict(changed=True, md5sum=new_md5, dest=dest, remote_md5sum=None, checksum=new_checksum, remote_checksum=remote_checksum))
return result
else:
# For backwards compatibility. We'll return None on FIPS enabled
# systems
@ -153,5 +182,5 @@ class ActionModule(ActionBase):
except ValueError:
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
class ActionModule(ActionBase):
''' Create inventory groups based on variables '''
### We need to be able to modify the inventory
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:
return dict(failed=True, msg="the 'key' param is required when using group_by")
result = super(ActionModule, self).run(tmp, task_vars)
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 = 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
from ansible.errors import AnsibleError
from ansible.parsing import DataLoader
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
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')
@ -43,7 +47,11 @@ class ActionModule(ActionBase):
data = {}
if not isinstance(data, dict):
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:
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
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
# 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)
__metaclass__ = type
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()):
def run(self, tmp=None, task_vars=None):
''' 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')
if module == 'auto':
@ -40,13 +42,15 @@ class ActionModule(ActionBase):
if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_pkg_mgr'), task_vars=task_vars)
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')
if module != 'auto':
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
new_module_args = self._task.args.copy()
@ -54,8 +58,9 @@ class ActionModule(ActionBase):
del new_module_args['use']
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:
return {'failed': True, 'msg': 'Could not detect which package manager to use. Try gathering facts or setting the "use" option.'}
result['failed'] = True
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.utils.boolean import boolean
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)
dest = self._task.args.get('dest', None)
remote_src = boolean(self._task.args.get('remote_src', 'no'))
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:
# everything is remote, so we just execute the module
# 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:
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.plugins.action import ActionBase
class AnsibleTimeoutExceeded(Exception):
pass
def timeout_handler(signum, frame):
raise AnsibleTimeoutExceeded
class ActionModule(ActionBase):
''' pauses execution for a length or time, or until input is received '''
PAUSE_TYPES = ['seconds', 'minutes', 'prompt', '']
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 '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
duration_unit = 'minutes'
prompt = None
seconds = None
result = dict(
result.update(dict(
changed = False,
rc = 0,
stderr = '',
@ -53,37 +60,37 @@ class ActionModule(ActionBase):
start = None,
stop = None,
delta = None,
)
))
# Is 'args' empty, then this is the default prompted pause
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()
# Are 'minutes' or 'seconds' keys that exist in 'args'?
elif 'minutes' in self._task.args or 'seconds' in self._task.args:
try:
if 'minutes' in self._task.args:
pause_type = 'minutes'
# The time() command operates in seconds so we need to
# recalculate for minutes=X values.
seconds = int(self._task.args['minutes']) * 60
else:
pause_type = 'seconds'
seconds = int(self._task.args['seconds'])
duration_unit = 'seconds'
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'?
elif 'prompt' in self._task.args:
pause_type = 'prompt'
prompt = "[%s]\n%s:" % (self._task.get_name().strip(), self._task.args['prompt'])
else:
# 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!

View file

@ -21,17 +21,23 @@ from ansible.plugins.action import ActionBase
import re
class ActionModule(ActionBase):
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:
# in --check mode, always skip this module execution
return dict(skipped=True)
result['skipped'] = True
return result
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
# 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.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = True
def run(self, tmp=None, task_vars=None):
''' 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:
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:
tmp = self._make_tmp_path()
@ -39,8 +46,8 @@ class ActionModule(ActionBase):
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
result = 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)
res = self._execute_module(module_name='stat', module_args=dict(path=creates), task_vars=task_vars, tmp=tmp, persist_files=True)
stat = res.get('stat', None)
if stat and stat.get('exists', False):
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
# and the filename does not exist. This allows idempotence
# of command executions.
result = 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)
res = self._execute_module(module_name='stat', module_args=dict(path=removes), task_vars=task_vars, tmp=tmp, persist_files=True)
stat = res.get('stat', None)
if stat and not stat.get('exists', False):
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()
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
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
def run(self, tmp=None, task_vars=dict()):
def run(self, tmp=None, task_vars=None):
''' 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')
if module == 'auto':
@ -41,7 +43,7 @@ class ActionModule(ActionBase):
if module == 'auto':
facts = self._execute_module(module_name='setup', module_args=dict(filter='ansible_service_mgr'), task_vars=task_vars)
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')
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']
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:
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.errors import AnsibleError
from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean
from ansible.utils.vars import isidentifier
@ -30,16 +29,26 @@ class ActionModule(ActionBase):
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()
if self._task.args:
for (k, v) in iteritems(self._task.args):
k = self._templar.template(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'):
v = boolean(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"):
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 '''
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
remote_transport = False
@ -124,7 +128,7 @@ class ActionModule(ActionBase):
# ansible's delegate_to mechanism to determine which host rsync is
# running on so localhost could be a non-controller machine if
# delegate_to is used)
src_host = '127.0.0.1'
src_host = '127.0.0.1'
inventory_hostname = task_vars.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)
@ -236,7 +240,7 @@ class ActionModule(ActionBase):
self._task.args['ssh_args'] = C.ANSIBLE_SSH_ARGS
# 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']:
# Emit a warning about using python3 because synchronize is

View file

@ -17,7 +17,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import base64
import datetime
import os
import pwd
@ -29,6 +28,7 @@ from ansible.utils.hashing import checksum_s
from ansible.utils.boolean import boolean
from ansible.utils.unicode import to_bytes, to_unicode
class ActionModule(ActionBase):
TRANSFERS_FILES = True
@ -52,8 +52,12 @@ class ActionModule(ActionBase):
return remote_checksum
def run(self, tmp=None, task_vars=dict()):
def run(self, tmp=None, task_vars=None):
''' 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)
dest = self._task.args.get('dest', None)
@ -61,7 +65,9 @@ class ActionModule(ActionBase):
force = boolean(self._task.args.get('force', False))
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:
tmp = self._make_tmp_path()
@ -69,7 +75,9 @@ class ActionModule(ActionBase):
if faf:
source = self._get_first_available_file(faf, task_vars.get('_original_file', None, 'templates'))
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:
if self._task._role is not None:
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)
self._templar.set_available_variables(old_vars)
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)
remote_checksum = self.get_checksum(dest, task_vars, not directory_prepended, source=source)
if isinstance(remote_checksum, dict):
# Error from remote_checksum is a dict. Valid return is a str
return remote_checksum
result.update(remote_checksum)
return result
diff = {}
new_module_args = self._task.args.copy()
if local_checksum != remote_checksum:
dest_contents = ''
# if showing diffs, we need to get the remote value
if self._play_context.diff:
diff = self._get_diff_data(dest, resultant, task_vars, source_file=False)
@ -162,12 +171,12 @@ class ActionModule(ActionBase):
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:
if remote_checksum == '1' or force:
result = dict(changed=True)
result['changed'] = True
else:
result = dict(changed=False)
result['changed'] = False
if result.get('changed', False) and self._play_context.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
import os
import pipes
from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean
@ -29,8 +28,12 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = True
def run(self, tmp=None, task_vars=dict()):
def run(self, tmp=None, task_vars=None):
''' 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)
dest = self._task.args.get('dest', None)
@ -38,7 +41,9 @@ class ActionModule(ActionBase):
creates = self._task.args.get('creates', 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:
tmp = self._make_tmp_path()
@ -47,11 +52,12 @@ class ActionModule(ActionBase):
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# 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)
stat = result.get('stat', None)
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.
source = os.path.expanduser(source)
@ -68,9 +74,13 @@ class ActionModule(ActionBase):
remote_checksum = self._remote_checksum(dest, all_vars=task_vars)
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':
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:
# transfer the file to a remote tmp location
@ -103,5 +113,5 @@ class ActionModule(ActionBase):
)
# 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.copy import ActionModule as CopyActionModule
# Even though CopyActionModule inherits from ActionBase, we still need to
# directly inherit from ActionBase to appease the plugin loader.
class ActionModule(CopyActionModule, ActionBase):

View file

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

View file

@ -61,20 +61,26 @@ class CallbackModule(CallbackBase):
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')
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):
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')
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)
def v2_runner_on_skipped(self, result):
self._display.display("%s | SKIPPED" % (result._host.get_name()), color='cyan')
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):
if 'diff' in result._result and result._result['diff']:

View file

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

View file

@ -29,9 +29,15 @@ from ansible.plugins.action import ActionBase
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):
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, '', '')))
play_context.become = True