2014-10-02 17:07:05 +00:00
|
|
|
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
2017-11-09 20:04:40 +00:00
|
|
|
# (c) 2017 Ansible Project
|
|
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
2014-10-15 23:18:12 +00:00
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
|
|
|
|
2017-11-09 20:04:40 +00:00
|
|
|
import os
|
2018-12-06 15:49:01 +00:00
|
|
|
import re
|
2017-11-09 20:04:40 +00:00
|
|
|
import pty
|
2015-04-27 19:43:25 +00:00
|
|
|
import time
|
2017-11-09 20:04:40 +00:00
|
|
|
import json
|
|
|
|
import subprocess
|
2018-03-05 14:12:01 +00:00
|
|
|
import sys
|
2018-08-10 13:26:58 +00:00
|
|
|
import termios
|
2016-02-10 14:22:57 +00:00
|
|
|
import traceback
|
2015-04-27 19:43:25 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
from ansible import constants as C
|
2017-04-21 20:17:12 +00:00
|
|
|
from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleConnectionFailure, AnsibleActionFail, AnsibleActionSkip
|
2016-02-23 20:07:06 +00:00
|
|
|
from ansible.executor.task_result import TaskResult
|
2017-03-23 20:35:05 +00:00
|
|
|
from ansible.module_utils.six import iteritems, string_types, binary_type
|
2019-01-31 23:44:57 +00:00
|
|
|
from ansible.module_utils.six.moves import xrange
|
2018-02-14 20:52:19 +00:00
|
|
|
from ansible.module_utils._text import to_text, to_native
|
2018-08-10 13:26:58 +00:00
|
|
|
from ansible.module_utils.connection import write_to_file_descriptor
|
2015-01-15 07:13:45 +00:00
|
|
|
from ansible.playbook.conditional import Conditional
|
2015-01-02 13:51:15 +00:00
|
|
|
from ansible.playbook.task import Task
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
from ansible.plugins.loader import become_loader
|
2015-05-02 04:48:11 +00:00
|
|
|
from ansible.template import Templar
|
2015-01-15 07:13:45 +00:00
|
|
|
from ansible.utils.listify import listify_lookup_plugin_terms
|
2017-04-26 22:09:36 +00:00
|
|
|
from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var
|
2017-10-29 04:33:02 +00:00
|
|
|
from ansible.vars.clean import namespace_facts, clean_facts
|
2018-11-20 23:06:51 +00:00
|
|
|
from ansible.utils.display import Display
|
2017-11-16 18:49:57 +00:00
|
|
|
from ansible.utils.vars import combine_vars
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2018-11-20 23:06:51 +00:00
|
|
|
display = Display()
|
2015-11-11 16:18:26 +00:00
|
|
|
|
2017-03-23 20:35:05 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
__all__ = ['TaskExecutor']
|
|
|
|
|
2015-11-11 16:18:26 +00:00
|
|
|
|
2018-01-26 17:07:04 +00:00
|
|
|
def remove_omit(task_args, omit_token):
|
|
|
|
'''
|
|
|
|
Remove args with a value equal to the ``omit_token`` recursively
|
|
|
|
to align with now having suboptions in the argument_spec
|
|
|
|
'''
|
|
|
|
|
2018-11-07 19:41:22 +00:00
|
|
|
if not isinstance(task_args, dict):
|
|
|
|
return task_args
|
|
|
|
|
|
|
|
new_args = {}
|
2018-01-26 17:07:04 +00:00
|
|
|
for i in iteritems(task_args):
|
|
|
|
if i[1] == omit_token:
|
|
|
|
continue
|
|
|
|
elif isinstance(i[1], dict):
|
|
|
|
new_args[i[0]] = remove_omit(i[1], omit_token)
|
2018-11-07 19:41:22 +00:00
|
|
|
elif isinstance(i[1], list):
|
|
|
|
new_args[i[0]] = [remove_omit(v, omit_token) for v in i[1]]
|
2018-01-26 17:07:04 +00:00
|
|
|
else:
|
|
|
|
new_args[i[0]] = i[1]
|
|
|
|
|
|
|
|
return new_args
|
|
|
|
|
|
|
|
|
2014-10-15 23:37:29 +00:00
|
|
|
class TaskExecutor:
|
2014-10-15 22:53:43 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
'''
|
|
|
|
This is the main worker class for the executor pipeline, which
|
|
|
|
handles loading an action plugin to actually dispatch the task to
|
|
|
|
a given host. This class roughly corresponds to the old Runner()
|
|
|
|
class.
|
|
|
|
'''
|
|
|
|
|
2015-06-11 15:54:25 +00:00
|
|
|
# Modules that we optimize by squashing loop items into a single call to
|
|
|
|
# the module
|
2015-07-07 15:59:20 +00:00
|
|
|
SQUASH_ACTIONS = frozenset(C.DEFAULT_SQUASH_ACTIONS)
|
2015-06-11 15:54:25 +00:00
|
|
|
|
2018-08-13 18:43:28 +00:00
|
|
|
def __init__(self, host, task, job_vars, play_context, new_stdin, loader, shared_loader_obj, final_q):
|
2017-05-30 17:13:53 +00:00
|
|
|
self._host = host
|
|
|
|
self._task = task
|
|
|
|
self._job_vars = job_vars
|
|
|
|
self._play_context = play_context
|
|
|
|
self._new_stdin = new_stdin
|
|
|
|
self._loader = loader
|
2015-05-02 04:48:11 +00:00
|
|
|
self._shared_loader_obj = shared_loader_obj
|
2017-05-30 17:13:53 +00:00
|
|
|
self._connection = None
|
2018-08-13 18:43:28 +00:00
|
|
|
self._final_q = final_q
|
2017-05-30 17:13:53 +00:00
|
|
|
self._loop_eval_error = None
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2016-08-25 16:34:08 +00:00
|
|
|
self._task.squash()
|
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
def run(self):
|
|
|
|
'''
|
|
|
|
The main executor entrypoint, where we determine if the specified
|
2016-03-23 07:20:27 +00:00
|
|
|
task requires looping and either runs the task with self._run_loop()
|
|
|
|
or self._execute(). After that, the returned results are parsed and
|
|
|
|
returned as a dict.
|
2014-11-14 22:14:08 +00:00
|
|
|
'''
|
|
|
|
|
2017-07-19 20:02:32 +00:00
|
|
|
display.debug("in run() - task %s" % self._task._uuid)
|
2015-01-08 16:51:54 +00:00
|
|
|
|
|
|
|
try:
|
2016-11-15 16:02:07 +00:00
|
|
|
try:
|
|
|
|
items = self._get_loop_items()
|
|
|
|
except AnsibleUndefinedVariable as e:
|
|
|
|
# save the error raised here for use later
|
|
|
|
items = None
|
|
|
|
self._loop_eval_error = e
|
|
|
|
|
2015-01-09 15:37:31 +00:00
|
|
|
if items is not None:
|
2015-01-08 16:51:54 +00:00
|
|
|
if len(items) > 0:
|
|
|
|
item_results = self._run_loop(items)
|
2015-04-02 16:54:45 +00:00
|
|
|
|
2017-06-01 15:36:04 +00:00
|
|
|
# create the overall result item
|
2015-01-08 16:51:54 +00:00
|
|
|
res = dict(results=item_results)
|
2015-04-02 16:54:45 +00:00
|
|
|
|
2017-06-01 15:36:04 +00:00
|
|
|
# loop through the item results, and set the global changed/failed result flags based on any item.
|
|
|
|
for item in item_results:
|
|
|
|
if 'changed' in item and item['changed'] and not res.get('changed'):
|
|
|
|
res['changed'] = True
|
2017-11-03 14:05:12 +00:00
|
|
|
if 'failed' in item and item['failed']:
|
|
|
|
item_ignore = item.pop('_ansible_ignore_errors')
|
|
|
|
if not res.get('failed'):
|
|
|
|
res['failed'] = True
|
|
|
|
res['msg'] = 'One or more items failed'
|
|
|
|
self._task.ignore_errors = item_ignore
|
|
|
|
elif self._task.ignore_errors and not item_ignore:
|
|
|
|
self._task.ignore_errors = item_ignore
|
2017-06-01 15:36:04 +00:00
|
|
|
|
|
|
|
# ensure to accumulate these
|
|
|
|
for array in ['warnings', 'deprecations']:
|
|
|
|
if array in item and item[array]:
|
|
|
|
if array not in res:
|
|
|
|
res[array] = []
|
|
|
|
if not isinstance(item[array], list):
|
|
|
|
item[array] = [item[array]]
|
|
|
|
res[array] = res[array] + item[array]
|
|
|
|
del item[array]
|
|
|
|
|
|
|
|
if not res.get('Failed', False):
|
2015-04-02 16:54:45 +00:00
|
|
|
res['msg'] = 'All items completed'
|
2015-01-08 16:51:54 +00:00
|
|
|
else:
|
|
|
|
res = dict(changed=False, skipped=True, skipped_reason='No items in the list', results=[])
|
2014-11-14 22:14:08 +00:00
|
|
|
else:
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("calling self._execute()")
|
2015-01-08 16:51:54 +00:00
|
|
|
res = self._execute()
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("_execute() done")
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-01-15 22:56:54 +00:00
|
|
|
# make sure changed is set in the result, if it's not present
|
|
|
|
if 'changed' not in res:
|
|
|
|
res['changed'] = False
|
|
|
|
|
2017-02-24 18:01:21 +00:00
|
|
|
def _clean_res(res, errors='surrogate_or_strict'):
|
2016-08-24 14:52:36 +00:00
|
|
|
if isinstance(res, UnsafeProxy):
|
|
|
|
return res._obj
|
|
|
|
elif isinstance(res, binary_type):
|
2017-02-24 18:01:21 +00:00
|
|
|
return to_text(res, errors=errors)
|
2016-08-24 14:52:36 +00:00
|
|
|
elif isinstance(res, dict):
|
|
|
|
for k in res:
|
2017-02-24 18:01:21 +00:00
|
|
|
try:
|
|
|
|
res[k] = _clean_res(res[k], errors=errors)
|
|
|
|
except UnicodeError:
|
|
|
|
if k == 'diff':
|
|
|
|
# If this is a diff, substitute a replacement character if the value
|
|
|
|
# is undecodable as utf8. (Fix #21804)
|
2017-02-27 17:40:58 +00:00
|
|
|
display.warning("We were unable to decode all characters in the module return data."
|
|
|
|
" Replaced some in an effort to return as much as possible")
|
2017-02-24 18:01:21 +00:00
|
|
|
res[k] = _clean_res(res[k], errors='surrogate_then_replace')
|
|
|
|
else:
|
|
|
|
raise
|
2015-09-17 20:04:47 +00:00
|
|
|
elif isinstance(res, list):
|
2017-08-18 12:55:48 +00:00
|
|
|
for idx, item in enumerate(res):
|
2017-02-24 18:01:21 +00:00
|
|
|
res[idx] = _clean_res(item, errors=errors)
|
2015-09-17 20:04:47 +00:00
|
|
|
return res
|
|
|
|
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("dumping result to json")
|
2015-09-17 20:04:47 +00:00
|
|
|
res = _clean_res(res)
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("done dumping result, returning")
|
2015-09-17 20:04:47 +00:00
|
|
|
return res
|
2015-08-27 06:16:11 +00:00
|
|
|
except AnsibleError as e:
|
2018-08-27 18:19:06 +00:00
|
|
|
return dict(failed=True, msg=wrap_var(to_text(e, nonstring='simplerepr')), _ansible_no_log=self._play_context.no_log)
|
2016-02-10 14:22:57 +00:00
|
|
|
except Exception as e:
|
2018-08-22 01:53:56 +00:00
|
|
|
return dict(failed=True, msg='Unexpected failure during module execution.', exception=to_text(traceback.format_exc()),
|
2018-08-27 18:19:06 +00:00
|
|
|
stdout='', _ansible_no_log=self._play_context.no_log)
|
2015-09-09 18:21:56 +00:00
|
|
|
finally:
|
|
|
|
try:
|
|
|
|
self._connection.close()
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
except Exception as e:
|
2016-09-07 05:54:17 +00:00
|
|
|
display.debug(u"error closing connection: %s" % to_text(e))
|
2014-11-14 22:14:08 +00:00
|
|
|
|
|
|
|
def _get_loop_items(self):
|
|
|
|
'''
|
|
|
|
Loads a lookup plugin to handle the with_* portion of a task (if specified),
|
|
|
|
and returns the items result.
|
|
|
|
'''
|
|
|
|
|
2015-11-19 00:12:38 +00:00
|
|
|
# save the play context variables to a temporary dictionary,
|
|
|
|
# so that we can modify the job vars without doing a full copy
|
|
|
|
# and later restore them to avoid modifying things too early
|
|
|
|
play_context_vars = dict()
|
|
|
|
self._play_context.update_vars(play_context_vars)
|
|
|
|
|
|
|
|
old_vars = dict()
|
2016-08-24 14:52:36 +00:00
|
|
|
for k in play_context_vars:
|
2015-11-19 00:12:38 +00:00
|
|
|
if k in self._job_vars:
|
|
|
|
old_vars[k] = self._job_vars[k]
|
|
|
|
self._job_vars[k] = play_context_vars[k]
|
|
|
|
|
2016-10-17 14:32:28 +00:00
|
|
|
# get search path for this task to pass to lookup plugins
|
|
|
|
self._job_vars['ansible_search_path'] = self._task.get_search_path()
|
|
|
|
|
2018-07-31 15:51:32 +00:00
|
|
|
# ensure basedir is always in (dwim already searches here but we need to display it)
|
|
|
|
if self._loader.get_basedir() not in self._job_vars['ansible_search_path']:
|
|
|
|
self._job_vars['ansible_search_path'].append(self._loader.get_basedir())
|
|
|
|
|
2015-11-19 00:12:38 +00:00
|
|
|
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=self._job_vars)
|
2014-11-14 22:14:08 +00:00
|
|
|
items = None
|
2018-10-18 20:25:43 +00:00
|
|
|
loop_cache = self._job_vars.get('_ansible_loop_cache')
|
|
|
|
if loop_cache is not None:
|
|
|
|
# _ansible_loop_cache may be set in `get_vars` when calculating `delegate_to`
|
|
|
|
# to avoid reprocessing the loop
|
|
|
|
items = loop_cache
|
|
|
|
elif self._task.loop_with:
|
move from with_<lookup>: to loop:
- old functionality is still available direct lookup use, the following are equivalent
with_nested: [[1,2,3], ['a','b','c']]
loop: "{{lookup('nested', [1,2,3], ['a','b','c'])}}"
- avoid squashing with 'loop:'
- fixed test to use new intenal attributes
- removed most of 'lookup docs' as these now reside in the plugins
2017-09-17 03:32:34 +00:00
|
|
|
if self._task.loop_with in self._shared_loader_obj.lookup_loader:
|
2017-04-13 15:21:11 +00:00
|
|
|
fail = True
|
move from with_<lookup>: to loop:
- old functionality is still available direct lookup use, the following are equivalent
with_nested: [[1,2,3], ['a','b','c']]
loop: "{{lookup('nested', [1,2,3], ['a','b','c'])}}"
- avoid squashing with 'loop:'
- fixed test to use new intenal attributes
- removed most of 'lookup docs' as these now reside in the plugins
2017-09-17 03:32:34 +00:00
|
|
|
if self._task.loop_with == 'first_found':
|
2016-09-16 16:54:52 +00:00
|
|
|
# first_found loops are special. If the item is undefined then we want to fall through to the next value rather than failing.
|
2017-04-13 15:21:11 +00:00
|
|
|
fail = False
|
|
|
|
|
move from with_<lookup>: to loop:
- old functionality is still available direct lookup use, the following are equivalent
with_nested: [[1,2,3], ['a','b','c']]
loop: "{{lookup('nested', [1,2,3], ['a','b','c'])}}"
- avoid squashing with 'loop:'
- fixed test to use new intenal attributes
- removed most of 'lookup docs' as these now reside in the plugins
2017-09-17 03:32:34 +00:00
|
|
|
loop_terms = listify_lookup_plugin_terms(terms=self._task.loop, templar=templar, loader=self._loader, fail_on_undefined=fail,
|
2017-04-13 15:21:11 +00:00
|
|
|
convert_bare=False)
|
|
|
|
if not fail:
|
2015-11-09 18:51:54 +00:00
|
|
|
loop_terms = [t for t in loop_terms if not templar._contains_vars(t)]
|
2016-07-13 14:06:34 +00:00
|
|
|
|
|
|
|
# get lookup
|
move from with_<lookup>: to loop:
- old functionality is still available direct lookup use, the following are equivalent
with_nested: [[1,2,3], ['a','b','c']]
loop: "{{lookup('nested', [1,2,3], ['a','b','c'])}}"
- avoid squashing with 'loop:'
- fixed test to use new intenal attributes
- removed most of 'lookup docs' as these now reside in the plugins
2017-09-17 03:32:34 +00:00
|
|
|
mylookup = self._shared_loader_obj.lookup_loader.get(self._task.loop_with, loader=self._loader, templar=templar)
|
2016-07-13 14:06:34 +00:00
|
|
|
|
|
|
|
# give lookup task 'context' for subdir (mostly needed for first_found)
|
2016-09-07 05:54:17 +00:00
|
|
|
for subdir in ['template', 'var', 'file']: # TODO: move this to constants?
|
2016-08-08 18:25:59 +00:00
|
|
|
if subdir in self._task.action:
|
2016-07-13 14:06:34 +00:00
|
|
|
break
|
2017-05-30 17:13:53 +00:00
|
|
|
setattr(mylookup, '_subdir', subdir + 's')
|
2016-07-13 14:06:34 +00:00
|
|
|
|
|
|
|
# run lookup
|
|
|
|
items = mylookup.run(terms=loop_terms, variables=self._job_vars, wantlist=True)
|
2015-08-04 16:10:23 +00:00
|
|
|
else:
|
move from with_<lookup>: to loop:
- old functionality is still available direct lookup use, the following are equivalent
with_nested: [[1,2,3], ['a','b','c']]
loop: "{{lookup('nested', [1,2,3], ['a','b','c'])}}"
- avoid squashing with 'loop:'
- fixed test to use new intenal attributes
- removed most of 'lookup docs' as these now reside in the plugins
2017-09-17 03:32:34 +00:00
|
|
|
raise AnsibleError("Unexpected failure in finding the lookup named '%s' in the available lookup plugins" % self._task.loop_with)
|
|
|
|
|
2018-10-16 18:35:10 +00:00
|
|
|
elif self._task.loop is not None:
|
move from with_<lookup>: to loop:
- old functionality is still available direct lookup use, the following are equivalent
with_nested: [[1,2,3], ['a','b','c']]
loop: "{{lookup('nested', [1,2,3], ['a','b','c'])}}"
- avoid squashing with 'loop:'
- fixed test to use new intenal attributes
- removed most of 'lookup docs' as these now reside in the plugins
2017-09-17 03:32:34 +00:00
|
|
|
items = templar.template(self._task.loop)
|
|
|
|
if not isinstance(items, list):
|
2018-04-09 09:08:04 +00:00
|
|
|
raise AnsibleError(
|
|
|
|
"Invalid data passed to 'loop', it requires a list, got this instead: %s."
|
|
|
|
" Hint: If you passed a list/dict of just one element,"
|
|
|
|
" try adding wantlist=True to your lookup invocation or use q/query instead of lookup." % items
|
|
|
|
)
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-11-19 00:12:38 +00:00
|
|
|
# now we restore any old job variables that may have been modified,
|
|
|
|
# and delete them if they were in the play context vars but not in
|
|
|
|
# the old variables dictionary
|
2016-08-24 14:52:36 +00:00
|
|
|
for k in play_context_vars:
|
2015-11-19 00:12:38 +00:00
|
|
|
if k in old_vars:
|
|
|
|
self._job_vars[k] = old_vars[k]
|
|
|
|
else:
|
|
|
|
del self._job_vars[k]
|
|
|
|
|
2015-09-08 16:18:10 +00:00
|
|
|
if items:
|
|
|
|
for idx, item in enumerate(items):
|
|
|
|
if item is not None and not isinstance(item, UnsafeProxy):
|
|
|
|
items[idx] = UnsafeProxy(item)
|
2016-10-17 14:32:28 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
return items
|
|
|
|
|
|
|
|
def _run_loop(self, items):
|
|
|
|
'''
|
|
|
|
Runs the task with the loop items specified and collates the result
|
|
|
|
into an array named 'results' which is inserted into the final result
|
|
|
|
along with the item for which the loop ran.
|
|
|
|
'''
|
|
|
|
|
|
|
|
results = []
|
|
|
|
|
2015-01-20 07:16:19 +00:00
|
|
|
# make copies of the job vars and task so we can add the item to
|
|
|
|
# the variables and re-validate the task with the item variable
|
2017-05-30 17:13:53 +00:00
|
|
|
# task_vars = self._job_vars.copy()
|
2015-11-04 16:26:06 +00:00
|
|
|
task_vars = self._job_vars
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-10-23 07:27:09 +00:00
|
|
|
loop_var = 'item'
|
2018-01-26 21:29:04 +00:00
|
|
|
index_var = None
|
2016-08-31 13:59:43 +00:00
|
|
|
label = None
|
2016-08-31 20:09:37 +00:00
|
|
|
loop_pause = 0
|
2018-12-07 19:49:50 +00:00
|
|
|
extended = False
|
2018-02-14 20:52:19 +00:00
|
|
|
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=self._job_vars)
|
2018-06-21 20:14:57 +00:00
|
|
|
|
|
|
|
# FIXME: move this to the object itself to allow post_validate to take care of templating (loop_control.post_validate)
|
2015-10-23 07:27:09 +00:00
|
|
|
if self._task.loop_control:
|
2018-02-14 20:52:19 +00:00
|
|
|
loop_var = templar.template(self._task.loop_control.loop_var)
|
|
|
|
index_var = templar.template(self._task.loop_control.index_var)
|
|
|
|
loop_pause = templar.template(self._task.loop_control.pause)
|
2018-12-07 19:49:50 +00:00
|
|
|
extended = templar.template(self._task.loop_control.extended)
|
2018-06-21 20:14:57 +00:00
|
|
|
|
|
|
|
# This may be 'None',so it is tempalted below after we ensure a value and an item is assigned
|
|
|
|
label = self._task.loop_control.label
|
|
|
|
|
|
|
|
# ensure we always have a label
|
|
|
|
if label is None:
|
|
|
|
label = '{{' + loop_var + '}}'
|
2015-10-23 07:27:09 +00:00
|
|
|
|
|
|
|
if loop_var in task_vars:
|
2016-09-12 12:35:23 +00:00
|
|
|
display.warning(u"The loop variable '%s' is already in use. "
|
2017-05-30 17:13:53 +00:00
|
|
|
u"You should set the `loop_var` value in the `loop_control` option for the task"
|
|
|
|
u" to something else to avoid variable collisions and unexpected behavior." % loop_var)
|
2015-10-23 07:27:09 +00:00
|
|
|
|
2016-08-31 20:09:37 +00:00
|
|
|
ran_once = False
|
move from with_<lookup>: to loop:
- old functionality is still available direct lookup use, the following are equivalent
with_nested: [[1,2,3], ['a','b','c']]
loop: "{{lookup('nested', [1,2,3], ['a','b','c'])}}"
- avoid squashing with 'loop:'
- fixed test to use new intenal attributes
- removed most of 'lookup docs' as these now reside in the plugins
2017-09-17 03:32:34 +00:00
|
|
|
if self._task.loop_with:
|
|
|
|
# Only squash with 'with_:' not with the 'loop:', 'magic' squashing can be removed once with_ loops are
|
|
|
|
items = self._squash_items(items, loop_var, task_vars)
|
|
|
|
|
2018-08-22 01:53:56 +00:00
|
|
|
no_log = False
|
2018-12-07 19:49:50 +00:00
|
|
|
items_len = len(items)
|
2018-01-26 21:29:04 +00:00
|
|
|
for item_index, item in enumerate(items):
|
2015-10-23 07:27:09 +00:00
|
|
|
task_vars[loop_var] = item
|
2018-01-26 21:29:04 +00:00
|
|
|
if index_var:
|
|
|
|
task_vars[index_var] = item_index
|
2015-01-08 16:51:54 +00:00
|
|
|
|
2018-12-07 19:49:50 +00:00
|
|
|
if extended:
|
|
|
|
task_vars['ansible_loop'] = {
|
|
|
|
'allitems': items,
|
|
|
|
'index': item_index + 1,
|
|
|
|
'index0': item_index,
|
|
|
|
'first': item_index == 0,
|
|
|
|
'last': item_index + 1 == items_len,
|
|
|
|
'length': items_len,
|
|
|
|
'revindex': items_len - item_index,
|
|
|
|
'revindex0': items_len - item_index - 1,
|
|
|
|
}
|
|
|
|
try:
|
|
|
|
task_vars['ansible_loop']['nextitem'] = items[item_index + 1]
|
|
|
|
except IndexError:
|
|
|
|
pass
|
|
|
|
if item_index - 1 >= 0:
|
|
|
|
task_vars['ansible_loop']['previtem'] = items[item_index - 1]
|
|
|
|
|
2018-05-07 20:51:46 +00:00
|
|
|
# Update template vars to reflect current loop iteration
|
|
|
|
templar.set_available_variables(task_vars)
|
|
|
|
|
2016-08-31 20:09:37 +00:00
|
|
|
# pause between loop iterations
|
|
|
|
if loop_pause and ran_once:
|
2018-02-14 20:52:19 +00:00
|
|
|
try:
|
|
|
|
time.sleep(float(loop_pause))
|
|
|
|
except ValueError as e:
|
|
|
|
raise AnsibleError('Invalid pause value: %s, produced error: %s' % (loop_pause, to_native(e)))
|
2016-08-31 20:09:37 +00:00
|
|
|
else:
|
|
|
|
ran_once = True
|
|
|
|
|
2015-01-08 16:51:54 +00:00
|
|
|
try:
|
2016-08-04 14:08:36 +00:00
|
|
|
tmp_task = self._task.copy(exclude_parent=True, exclude_tasks=True)
|
|
|
|
tmp_task._parent = self._task._parent
|
2015-09-03 12:11:30 +00:00
|
|
|
tmp_play_context = self._play_context.copy()
|
2015-08-27 06:16:11 +00:00
|
|
|
except AnsibleParserError as e:
|
2016-09-07 05:54:17 +00:00
|
|
|
results.append(dict(failed=True, msg=to_text(e)))
|
2015-01-08 16:51:54 +00:00
|
|
|
continue
|
|
|
|
|
2015-09-03 12:11:30 +00:00
|
|
|
# now we swap the internal task and play context with their copies,
|
|
|
|
# execute, and swap them back so we can do the next iteration cleanly
|
2015-01-08 16:51:54 +00:00
|
|
|
(self._task, tmp_task) = (tmp_task, self._task)
|
2015-09-03 12:11:30 +00:00
|
|
|
(self._play_context, tmp_play_context) = (tmp_play_context, self._play_context)
|
2015-01-12 22:04:56 +00:00
|
|
|
res = self._execute(variables=task_vars)
|
2017-11-03 14:05:12 +00:00
|
|
|
task_fields = self._task.dump_attrs()
|
2015-01-08 16:51:54 +00:00
|
|
|
(self._task, tmp_task) = (tmp_task, self._task)
|
2015-09-03 12:11:30 +00:00
|
|
|
(self._play_context, tmp_play_context) = (tmp_play_context, self._play_context)
|
2015-01-08 16:51:54 +00:00
|
|
|
|
2018-08-22 01:53:56 +00:00
|
|
|
# update 'general no_log' based on specific no_log
|
|
|
|
no_log = no_log or tmp_task.no_log
|
|
|
|
|
2015-01-08 16:51:54 +00:00
|
|
|
# now update the result with the item info, and append the result
|
|
|
|
# to the list of results
|
2015-10-23 07:27:09 +00:00
|
|
|
res[loop_var] = item
|
2018-01-26 21:29:04 +00:00
|
|
|
if index_var:
|
|
|
|
res[index_var] = item_index
|
2018-12-07 19:49:50 +00:00
|
|
|
if extended:
|
|
|
|
res['ansible_loop'] = task_vars['ansible_loop']
|
|
|
|
|
2016-02-23 20:07:06 +00:00
|
|
|
res['_ansible_item_result'] = True
|
2017-11-03 14:05:12 +00:00
|
|
|
res['_ansible_ignore_errors'] = task_fields.get('ignore_errors')
|
2016-02-23 20:07:06 +00:00
|
|
|
|
2018-06-21 20:14:57 +00:00
|
|
|
# gets templated here unlike rest of loop_control fields, depends on loop_var above
|
2018-11-20 20:29:44 +00:00
|
|
|
try:
|
|
|
|
res['_ansible_item_label'] = templar.template(label, cache=False)
|
|
|
|
except AnsibleUndefinedVariable as e:
|
|
|
|
res.update({
|
|
|
|
'failed': True,
|
|
|
|
'msg': 'Failed to template loop_control.label: %s' % to_text(e)
|
|
|
|
})
|
2016-08-31 13:59:43 +00:00
|
|
|
|
2018-08-13 18:43:28 +00:00
|
|
|
self._final_q.put(
|
2017-02-17 15:14:26 +00:00
|
|
|
TaskResult(
|
|
|
|
self._host.name,
|
|
|
|
self._task._uuid,
|
|
|
|
res,
|
2017-11-03 14:05:12 +00:00
|
|
|
task_fields=task_fields,
|
2017-02-17 15:14:26 +00:00
|
|
|
),
|
|
|
|
block=False,
|
|
|
|
)
|
2014-11-14 22:14:08 +00:00
|
|
|
results.append(res)
|
2015-10-23 07:27:09 +00:00
|
|
|
del task_vars[loop_var]
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2018-08-22 01:53:56 +00:00
|
|
|
self._task.no_log = no_log
|
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
return results
|
|
|
|
|
2015-10-23 07:27:09 +00:00
|
|
|
def _squash_items(self, items, loop_var, variables):
|
2015-01-20 07:16:19 +00:00
|
|
|
'''
|
|
|
|
Squash items down to a comma-separated list for certain modules which support it
|
|
|
|
(typically package management modules).
|
|
|
|
'''
|
2016-05-21 14:06:01 +00:00
|
|
|
name = None
|
2016-05-12 01:26:39 +00:00
|
|
|
try:
|
|
|
|
# _task.action could contain templatable strings (via action: and
|
|
|
|
# local_action:) Template it before comparing. If we don't end up
|
|
|
|
# optimizing it here, the templatable string might use template vars
|
|
|
|
# that aren't available until later (it could even use vars from the
|
|
|
|
# with_items loop) so don't make the templated string permanent yet.
|
|
|
|
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=variables)
|
|
|
|
task_action = self._task.action
|
|
|
|
if templar._contains_vars(task_action):
|
|
|
|
task_action = templar.template(task_action, fail_on_undefined=False)
|
|
|
|
|
|
|
|
if len(items) > 0 and task_action in self.SQUASH_ACTIONS:
|
|
|
|
if all(isinstance(o, string_types) for o in items):
|
|
|
|
final_items = []
|
|
|
|
|
2018-05-30 17:05:03 +00:00
|
|
|
found = None
|
2016-05-12 01:26:39 +00:00
|
|
|
for allowed in ['name', 'pkg', 'package']:
|
|
|
|
name = self._task.args.pop(allowed, None)
|
|
|
|
if name is not None:
|
2018-05-30 17:05:03 +00:00
|
|
|
found = allowed
|
2016-05-12 01:26:39 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
# This gets the information to check whether the name field
|
|
|
|
# contains a template that we can squash for
|
|
|
|
template_no_item = template_with_item = None
|
|
|
|
if name:
|
|
|
|
if templar._contains_vars(name):
|
|
|
|
variables[loop_var] = '\0$'
|
|
|
|
template_no_item = templar.template(name, variables, cache=False)
|
|
|
|
variables[loop_var] = '\0@'
|
|
|
|
template_with_item = templar.template(name, variables, cache=False)
|
|
|
|
del variables[loop_var]
|
|
|
|
|
|
|
|
# Check if the user is doing some operation that doesn't take
|
|
|
|
# name/pkg or the name/pkg field doesn't have any variables
|
|
|
|
# and thus the items can't be squashed
|
|
|
|
if template_no_item != template_with_item:
|
2018-12-06 15:49:01 +00:00
|
|
|
if self._task.loop_with and self._task.loop_with not in ('items', 'list'):
|
|
|
|
value_text = "\"{{ query('%s', %r) }}\"" % (self._task.loop_with, self._task.loop)
|
|
|
|
else:
|
|
|
|
value_text = '%r' % self._task.loop
|
|
|
|
# Without knowing the data structure well, it's easiest to strip python2 unicode
|
|
|
|
# literals after stringifying
|
|
|
|
value_text = re.sub(r"\bu'", "'", value_text)
|
|
|
|
|
2018-05-30 17:05:03 +00:00
|
|
|
display.deprecated(
|
|
|
|
'Invoking "%s" only once while using a loop via squash_actions is deprecated. '
|
2018-12-06 15:49:01 +00:00
|
|
|
'Instead of using a loop to supply multiple items and specifying `%s: "%s"`, '
|
|
|
|
'please use `%s: %s` and remove the loop' % (self._task.action, found, name, found, value_text),
|
2018-05-30 17:05:03 +00:00
|
|
|
version='2.11'
|
|
|
|
)
|
2016-05-12 01:26:39 +00:00
|
|
|
for item in items:
|
|
|
|
variables[loop_var] = item
|
|
|
|
if self._task.evaluate_conditional(templar, variables):
|
|
|
|
new_item = templar.template(name, cache=False)
|
|
|
|
final_items.append(new_item)
|
|
|
|
self._task.args['name'] = final_items
|
|
|
|
# Wrap this in a list so that the calling function loop
|
|
|
|
# executes exactly once
|
|
|
|
return [final_items]
|
|
|
|
else:
|
|
|
|
# Restore the name parameter
|
|
|
|
self._task.args['name'] = name
|
2017-05-30 17:13:53 +00:00
|
|
|
# elif:
|
2016-05-12 01:26:39 +00:00
|
|
|
# Right now we only optimize single entries. In the future we
|
|
|
|
# could optimize more types:
|
|
|
|
# * lists can be squashed together
|
|
|
|
# * dicts could squash entries that match in all cases except the
|
|
|
|
# name or pkg field.
|
2018-02-14 20:52:19 +00:00
|
|
|
except Exception:
|
2016-05-12 01:26:39 +00:00
|
|
|
# Squashing is an optimization. If it fails for any reason,
|
|
|
|
# simply use the unoptimized list of items.
|
2016-05-21 14:06:01 +00:00
|
|
|
|
|
|
|
# Restore the name parameter
|
|
|
|
if name is not None:
|
|
|
|
self._task.args['name'] = name
|
2015-11-05 02:46:47 +00:00
|
|
|
return items
|
2015-01-20 07:16:19 +00:00
|
|
|
|
2015-01-12 22:04:56 +00:00
|
|
|
def _execute(self, variables=None):
|
2014-11-14 22:14:08 +00:00
|
|
|
'''
|
|
|
|
The primary workhorse of the executor system, this runs the task
|
|
|
|
on the specified host (which may be the delegated_to host) and handles
|
|
|
|
the retry/until and block rescue/always execution
|
|
|
|
'''
|
|
|
|
|
2015-01-12 22:04:56 +00:00
|
|
|
if variables is None:
|
|
|
|
variables = self._job_vars
|
|
|
|
|
2015-05-02 04:48:11 +00:00
|
|
|
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=variables)
|
|
|
|
|
2015-10-29 23:23:42 +00:00
|
|
|
context_validation_error = None
|
|
|
|
try:
|
|
|
|
# apply the given task's information to the connection info,
|
|
|
|
# which may override some fields already set by the play or
|
|
|
|
# the options specified on the command line
|
|
|
|
self._play_context = self._play_context.set_task_and_variable_override(task=self._task, variables=variables, templar=templar)
|
|
|
|
|
|
|
|
# fields set from the play/task may be based on variables, so we have to
|
|
|
|
# do the same kind of post validation step on it here before we use it.
|
|
|
|
self._play_context.post_validate(templar=templar)
|
|
|
|
|
2016-01-18 18:36:40 +00:00
|
|
|
# now that the play context is finalized, if the remote_addr is not set
|
|
|
|
# default to using the host's address field as the remote address
|
|
|
|
if not self._play_context.remote_addr:
|
|
|
|
self._play_context.remote_addr = self._host.address
|
|
|
|
|
2015-10-29 23:23:42 +00:00
|
|
|
# We also add "magic" variables back into the variables dict to make sure
|
|
|
|
# a certain subset of variables exist.
|
|
|
|
self._play_context.update_vars(variables)
|
2017-08-20 15:20:30 +00:00
|
|
|
|
|
|
|
# FIXME: update connection/shell plugin options
|
2015-10-29 23:23:42 +00:00
|
|
|
except AnsibleError as e:
|
|
|
|
# save the error, which we'll raise later if we don't end up
|
|
|
|
# skipping this task during the conditional evaluation step
|
|
|
|
context_validation_error = e
|
2015-04-15 02:10:17 +00:00
|
|
|
|
2015-01-20 20:03:26 +00:00
|
|
|
# Evaluate the conditional (if any) for this task, which we do before running
|
|
|
|
# the final task post-validation. We do this before the post validation due to
|
|
|
|
# the fact that the conditional may specify that the task be skipped due to a
|
|
|
|
# variable not being present which would otherwise cause validation to fail
|
2015-10-22 03:14:27 +00:00
|
|
|
try:
|
|
|
|
if not self._task.evaluate_conditional(templar, variables):
|
2017-01-31 18:32:51 +00:00
|
|
|
display.debug("when evaluation is False, skipping this task")
|
|
|
|
return dict(changed=False, skipped=True, skip_reason='Conditional result was False', _ansible_no_log=self._play_context.no_log)
|
2016-11-23 18:40:30 +00:00
|
|
|
except AnsibleError:
|
|
|
|
# loop error takes precedence
|
2016-11-15 16:02:07 +00:00
|
|
|
if self._loop_eval_error is not None:
|
2017-09-19 06:20:32 +00:00
|
|
|
raise self._loop_eval_error # pylint: disable=raising-bad-type
|
2018-04-26 18:39:44 +00:00
|
|
|
raise
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2016-11-23 18:40:30 +00:00
|
|
|
# Not skipping, if we had loop error raised earlier we need to raise it now to halt the execution of this task
|
|
|
|
if self._loop_eval_error is not None:
|
2017-09-19 06:20:32 +00:00
|
|
|
raise self._loop_eval_error # pylint: disable=raising-bad-type
|
2016-11-23 18:40:30 +00:00
|
|
|
|
2015-10-29 23:23:42 +00:00
|
|
|
# if we ran into an error while setting up the PlayContext, raise it now
|
|
|
|
if context_validation_error is not None:
|
2017-09-19 06:20:32 +00:00
|
|
|
raise context_validation_error # pylint: disable=raising-bad-type
|
2015-10-29 23:23:42 +00:00
|
|
|
|
2015-10-19 17:40:47 +00:00
|
|
|
# if this task is a TaskInclude, we just return now with a success code so the
|
|
|
|
# main thread can expand the task list for the given host
|
2017-06-06 21:39:48 +00:00
|
|
|
if self._task.action in ('include', 'include_tasks'):
|
2015-10-20 18:19:38 +00:00
|
|
|
include_variables = self._task.args.copy()
|
|
|
|
include_file = include_variables.pop('_raw_params', None)
|
2015-10-19 17:40:47 +00:00
|
|
|
if not include_file:
|
|
|
|
return dict(failed=True, msg="No include file was specified to the include")
|
2015-10-20 18:19:38 +00:00
|
|
|
|
|
|
|
include_file = templar.template(include_file)
|
|
|
|
return dict(include=include_file, include_variables=include_variables)
|
2015-10-19 17:40:47 +00:00
|
|
|
|
2016-08-26 17:42:13 +00:00
|
|
|
# if this task is a IncludeRole, we just return now with a success code so the main thread can expand the task list for the given host
|
|
|
|
elif self._task.action == 'include_role':
|
|
|
|
include_variables = self._task.args.copy()
|
2018-01-04 14:23:35 +00:00
|
|
|
return dict(include_variables=include_variables)
|
2016-08-26 17:42:13 +00:00
|
|
|
|
2015-07-05 05:06:54 +00:00
|
|
|
# Now we do final validation on the task, which sets all fields to their final values.
|
2015-05-02 04:48:11 +00:00
|
|
|
self._task.post_validate(templar=templar)
|
2015-07-24 14:31:14 +00:00
|
|
|
if '_variable_params' in self._task.args:
|
|
|
|
variable_params = self._task.args.pop('_variable_params')
|
|
|
|
if isinstance(variable_params, dict):
|
2018-08-14 19:58:00 +00:00
|
|
|
if C.INJECT_FACTS_AS_VARS:
|
|
|
|
display.warning("Using a variable for a task's 'args' is unsafe in some situations "
|
|
|
|
"(see https://docs.ansible.com/ansible/devel/reference_appendices/faq.html#argsplat-unsafe)")
|
|
|
|
variable_params.update(self._task.args)
|
|
|
|
self._task.args = variable_params
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-07-09 12:23:43 +00:00
|
|
|
# get the connection and the handler for this execution
|
2017-03-23 01:50:28 +00:00
|
|
|
if (not self._connection or
|
|
|
|
not getattr(self._connection, 'connected', False) or
|
|
|
|
self._play_context.remote_addr != self._connection._play_context.remote_addr):
|
2015-11-23 16:53:05 +00:00
|
|
|
self._connection = self._get_connection(variables=variables, templar=templar)
|
2016-01-18 18:36:40 +00:00
|
|
|
else:
|
|
|
|
# if connection is reused, its _play_context is no longer valid and needs
|
|
|
|
# to be replaced with the one templated above, in case other data changed
|
|
|
|
self._connection._play_context = self._play_context
|
2015-07-09 12:23:43 +00:00
|
|
|
|
2017-11-16 18:49:57 +00:00
|
|
|
self._set_connection_options(variables, templar)
|
|
|
|
|
|
|
|
# get handler
|
2015-07-09 12:23:43 +00:00
|
|
|
self._handler = self._get_action_handler(connection=self._connection, templar=templar)
|
|
|
|
|
2018-04-05 15:44:51 +00:00
|
|
|
# Apply default params for action/module, if present
|
|
|
|
# These are collected as a list of dicts, so we need to merge them
|
|
|
|
module_defaults = {}
|
|
|
|
for default in self._task.module_defaults:
|
|
|
|
module_defaults.update(default)
|
|
|
|
if module_defaults:
|
|
|
|
module_defaults = templar.template(module_defaults)
|
|
|
|
if self._task.action in module_defaults:
|
|
|
|
tmp_args = module_defaults[self._task.action].copy()
|
|
|
|
tmp_args.update(self._task.args)
|
|
|
|
self._task.args = tmp_args
|
2018-08-23 01:33:27 +00:00
|
|
|
if self._task.action in C.config.module_defaults_groups:
|
|
|
|
for group in C.config.module_defaults_groups.get(self._task.action, []):
|
|
|
|
tmp_args = (module_defaults.get('group/{0}'.format(group)) or {}).copy()
|
|
|
|
tmp_args.update(self._task.args)
|
|
|
|
self._task.args = tmp_args
|
2018-04-05 15:44:51 +00:00
|
|
|
|
2015-01-23 03:45:25 +00:00
|
|
|
# And filter out any fields which were set to default(omit), and got the omit token value
|
|
|
|
omit_token = variables.get('omit')
|
|
|
|
if omit_token is not None:
|
2018-01-26 17:07:04 +00:00
|
|
|
self._task.args = remove_omit(self._task.args, omit_token)
|
2015-01-23 03:45:25 +00:00
|
|
|
|
2015-01-20 20:03:26 +00:00
|
|
|
# Read some values from the task, so that we can modify them if need be
|
2016-05-12 18:42:24 +00:00
|
|
|
if self._task.until:
|
2016-08-04 23:20:45 +00:00
|
|
|
retries = self._task.retries
|
2016-05-12 18:42:24 +00:00
|
|
|
if retries is None:
|
|
|
|
retries = 3
|
2016-06-23 23:07:11 +00:00
|
|
|
elif retries <= 0:
|
|
|
|
retries = 1
|
2016-08-04 23:20:45 +00:00
|
|
|
else:
|
|
|
|
retries += 1
|
2015-10-27 19:35:56 +00:00
|
|
|
else:
|
2014-11-14 22:14:08 +00:00
|
|
|
retries = 1
|
|
|
|
|
|
|
|
delay = self._task.delay
|
|
|
|
if delay < 0:
|
2015-01-07 17:44:52 +00:00
|
|
|
delay = 1
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-01-15 22:56:54 +00:00
|
|
|
# make a copy of the job vars here, in case we need to update them
|
|
|
|
# with the registered variable value later on when testing conditions
|
2015-11-11 19:23:08 +00:00
|
|
|
vars_copy = variables.copy()
|
2015-01-15 22:56:54 +00:00
|
|
|
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("starting attempt loop")
|
2014-11-14 22:14:08 +00:00
|
|
|
result = None
|
2019-01-31 23:44:57 +00:00
|
|
|
for attempt in xrange(1, retries + 1):
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("running the handler")
|
2015-09-09 19:26:40 +00:00
|
|
|
try:
|
|
|
|
result = self._handler.run(task_vars=variables)
|
2017-04-21 20:17:12 +00:00
|
|
|
except AnsibleActionSkip as e:
|
|
|
|
return dict(skipped=True, msg=to_text(e))
|
|
|
|
except AnsibleActionFail as e:
|
|
|
|
return dict(failed=True, msg=to_text(e))
|
2015-09-09 19:26:40 +00:00
|
|
|
except AnsibleConnectionFailure as e:
|
2016-09-07 05:54:17 +00:00
|
|
|
return dict(unreachable=True, msg=to_text(e))
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("handler run complete")
|
2015-01-02 13:51:15 +00:00
|
|
|
|
2016-02-23 20:07:06 +00:00
|
|
|
# preserve no log
|
|
|
|
result["_ansible_no_log"] = self._play_context.no_log
|
|
|
|
|
2015-12-18 15:58:55 +00:00
|
|
|
# update the local copy of vars with the registered value, if specified,
|
|
|
|
# or any facts which may have been generated by the module execution
|
|
|
|
if self._task.register:
|
2018-01-04 16:45:34 +00:00
|
|
|
vars_copy[self._task.register] = wrap_var(result)
|
2015-12-18 15:58:55 +00:00
|
|
|
|
2017-11-22 20:35:58 +00:00
|
|
|
if self._task.async_val > 0:
|
2017-04-20 16:04:08 +00:00
|
|
|
if self._task.poll > 0 and not result.get('skipped') and not result.get('failed'):
|
2016-08-08 17:53:36 +00:00
|
|
|
result = self._poll_async_result(result=result, templar=templar, task_vars=vars_copy)
|
2017-05-30 17:13:53 +00:00
|
|
|
# FIXME callback 'v2_runner_on_async_poll' here
|
2015-01-02 13:51:15 +00:00
|
|
|
|
2016-02-23 20:37:21 +00:00
|
|
|
# ensure no log is preserved
|
|
|
|
result["_ansible_no_log"] = self._play_context.no_log
|
|
|
|
|
2015-11-11 19:23:08 +00:00
|
|
|
# helper methods for use below in evaluating changed/failed_when
|
2015-09-15 17:37:07 +00:00
|
|
|
def _evaluate_changed_when_result(result):
|
2016-03-09 20:18:37 +00:00
|
|
|
if self._task.changed_when is not None and self._task.changed_when:
|
2015-11-11 19:23:08 +00:00
|
|
|
cond = Conditional(loader=self._loader)
|
2016-03-06 15:47:15 +00:00
|
|
|
cond.when = self._task.changed_when
|
2015-05-04 06:33:10 +00:00
|
|
|
result['changed'] = cond.evaluate_conditional(templar, vars_copy)
|
2015-09-15 17:37:07 +00:00
|
|
|
|
|
|
|
def _evaluate_failed_when_result(result):
|
2016-03-06 15:47:15 +00:00
|
|
|
if self._task.failed_when:
|
2015-11-11 19:23:08 +00:00
|
|
|
cond = Conditional(loader=self._loader)
|
2016-03-06 15:47:15 +00:00
|
|
|
cond.when = self._task.failed_when
|
2015-05-04 06:33:10 +00:00
|
|
|
failed_when_result = cond.evaluate_conditional(templar, vars_copy)
|
2015-01-15 22:56:54 +00:00
|
|
|
result['failed_when_result'] = result['failed'] = failed_when_result
|
2016-03-10 13:01:54 +00:00
|
|
|
else:
|
|
|
|
failed_when_result = False
|
|
|
|
return failed_when_result
|
2015-09-15 17:37:07 +00:00
|
|
|
|
2018-04-05 23:57:34 +00:00
|
|
|
if 'ansible_facts' in result:
|
2019-01-15 20:20:33 +00:00
|
|
|
if self._task.action in ('set_fact', 'include_vars'):
|
2018-04-05 23:57:34 +00:00
|
|
|
vars_copy.update(result['ansible_facts'])
|
|
|
|
else:
|
2018-08-20 20:08:29 +00:00
|
|
|
# TODO: cleaning of facts should eventually become part of taskresults instead of vars
|
2018-04-05 23:57:34 +00:00
|
|
|
vars_copy.update(namespace_facts(result['ansible_facts']))
|
|
|
|
if C.INJECT_FACTS_AS_VARS:
|
|
|
|
vars_copy.update(clean_facts(result['ansible_facts']))
|
2015-11-11 19:23:08 +00:00
|
|
|
|
2017-05-20 22:25:57 +00:00
|
|
|
# set the failed property if it was missing.
|
|
|
|
if 'failed' not in result:
|
2017-07-05 17:50:32 +00:00
|
|
|
# rc is here for backwards compatibility and modules that use it instead of 'failed'
|
|
|
|
if 'rc' in result and result['rc'] not in [0, "0"]:
|
|
|
|
result['failed'] = True
|
|
|
|
else:
|
|
|
|
result['failed'] = False
|
2017-05-20 22:25:57 +00:00
|
|
|
|
2018-01-04 16:45:34 +00:00
|
|
|
# Make attempts and retries available early to allow their use in changed/failed_when
|
2018-01-26 15:06:03 +00:00
|
|
|
if self._task.until:
|
|
|
|
result['attempts'] = attempt
|
2018-01-04 16:45:34 +00:00
|
|
|
|
2017-05-20 22:25:57 +00:00
|
|
|
# set the changed property if it was missing.
|
|
|
|
if 'changed' not in result:
|
|
|
|
result['changed'] = False
|
2015-11-11 19:23:08 +00:00
|
|
|
|
2018-01-04 16:45:34 +00:00
|
|
|
# re-update the local copy of vars with the registered value, if specified,
|
|
|
|
# or any facts which may have been generated by the module execution
|
|
|
|
# This gives changed/failed_when access to additional recently modified
|
|
|
|
# attributes of result
|
|
|
|
if self._task.register:
|
|
|
|
vars_copy[self._task.register] = wrap_var(result)
|
|
|
|
|
2015-11-11 19:23:08 +00:00
|
|
|
# if we didn't skip this task, use the helpers to evaluate the changed/
|
|
|
|
# failed_when properties
|
|
|
|
if 'skipped' not in result:
|
|
|
|
_evaluate_changed_when_result(result)
|
|
|
|
_evaluate_failed_when_result(result)
|
|
|
|
|
2016-05-12 18:42:24 +00:00
|
|
|
if retries > 1:
|
2015-11-11 19:23:08 +00:00
|
|
|
cond = Conditional(loader=self._loader)
|
2016-03-06 15:47:15 +00:00
|
|
|
cond.when = self._task.until
|
2015-09-15 17:37:07 +00:00
|
|
|
if cond.evaluate_conditional(templar, vars_copy):
|
2015-07-28 14:14:48 +00:00
|
|
|
break
|
2016-02-23 20:07:06 +00:00
|
|
|
else:
|
|
|
|
# no conditional check, or it failed, so sleep for the specified time
|
2016-05-12 18:42:24 +00:00
|
|
|
if attempt < retries:
|
|
|
|
result['_ansible_retry'] = True
|
|
|
|
result['retries'] = retries
|
|
|
|
display.debug('Retrying task, attempt %d of %d' % (attempt, retries))
|
2018-08-13 18:43:28 +00:00
|
|
|
self._final_q.put(TaskResult(self._host.name, self._task._uuid, result, task_fields=self._task.dump_attrs()), block=False)
|
2016-05-12 18:42:24 +00:00
|
|
|
time.sleep(delay)
|
2016-02-23 18:12:38 +00:00
|
|
|
else:
|
|
|
|
if retries > 1:
|
|
|
|
# we ran out of attempts, so mark the result as failed
|
2016-10-02 06:05:36 +00:00
|
|
|
result['attempts'] = retries - 1
|
2016-02-23 18:12:38 +00:00
|
|
|
result['failed'] = True
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-03-25 18:51:40 +00:00
|
|
|
# do the final update of the local variables here, for both registered
|
|
|
|
# values and any facts which may have been created
|
|
|
|
if self._task.register:
|
2015-12-19 17:49:06 +00:00
|
|
|
variables[self._task.register] = wrap_var(result)
|
2015-03-25 18:51:40 +00:00
|
|
|
|
2018-04-05 23:57:34 +00:00
|
|
|
if 'ansible_facts' in result:
|
|
|
|
if self._task.action in ('set_fact', 'include_vars'):
|
|
|
|
variables.update(result['ansible_facts'])
|
|
|
|
else:
|
2018-08-20 20:08:29 +00:00
|
|
|
# TODO: cleaning of facts should eventually become part of taskresults instead of vars
|
2018-04-05 23:57:34 +00:00
|
|
|
variables.update(namespace_facts(result['ansible_facts']))
|
|
|
|
if C.INJECT_FACTS_AS_VARS:
|
|
|
|
variables.update(clean_facts(result['ansible_facts']))
|
2015-03-25 18:51:40 +00:00
|
|
|
|
2015-07-17 16:02:26 +00:00
|
|
|
# save the notification target in the result, if it was specified, as
|
|
|
|
# this task may be running in a loop in which case the notification
|
|
|
|
# may be item-specific, ie. "notify: service {{item}}"
|
2015-07-17 18:44:05 +00:00
|
|
|
if self._task.notify is not None:
|
2015-07-27 18:01:02 +00:00
|
|
|
result['_ansible_notify'] = self._task.notify
|
2015-07-17 16:02:26 +00:00
|
|
|
|
2015-11-10 15:14:30 +00:00
|
|
|
# add the delegated vars to the result, so we can reference them
|
|
|
|
# on the results side without having to do any further templating
|
|
|
|
# FIXME: we only want a limited set of variables here, so this is currently
|
|
|
|
# hardcoded but should be possibly fixed if we want more or if
|
|
|
|
# there is another source of truth we can use
|
|
|
|
delegated_vars = variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict()).copy()
|
|
|
|
if len(delegated_vars) > 0:
|
2017-06-20 18:56:52 +00:00
|
|
|
result["_ansible_delegated_vars"] = {'ansible_delegated_host': self._task.delegate_to}
|
|
|
|
for k in ('ansible_host', ):
|
2015-11-10 15:14:30 +00:00
|
|
|
result["_ansible_delegated_vars"][k] = delegated_vars.get(k)
|
|
|
|
|
2015-03-25 18:51:40 +00:00
|
|
|
# and return
|
2015-11-11 16:18:26 +00:00
|
|
|
display.debug("attempt loop complete, returning result")
|
2014-11-14 22:14:08 +00:00
|
|
|
return result
|
|
|
|
|
2016-08-08 17:53:36 +00:00
|
|
|
def _poll_async_result(self, result, templar, task_vars=None):
|
2015-01-02 13:51:15 +00:00
|
|
|
'''
|
|
|
|
Polls for the specified JID to be complete
|
|
|
|
'''
|
|
|
|
|
2016-08-08 17:53:36 +00:00
|
|
|
if task_vars is None:
|
|
|
|
task_vars = self._job_vars
|
|
|
|
|
2015-01-07 17:44:52 +00:00
|
|
|
async_jid = result.get('ansible_job_id')
|
2015-01-02 13:51:15 +00:00
|
|
|
if async_jid is None:
|
|
|
|
return dict(failed=True, msg="No job id was returned by the async task")
|
|
|
|
|
2017-03-31 14:48:29 +00:00
|
|
|
# Create a new pseudo-task to run the async_status module, and run
|
2015-01-02 13:51:15 +00:00
|
|
|
# that (with a sleep for "poll" seconds between each retry) until the
|
|
|
|
# async time limit is exceeded.
|
|
|
|
|
2018-05-04 23:43:33 +00:00
|
|
|
async_task = Task().load(dict(action='async_status jid=%s' % async_jid, environment=self._task.environment))
|
2015-01-02 13:51:15 +00:00
|
|
|
|
2017-05-30 17:13:53 +00:00
|
|
|
# FIXME: this is no longer the case, normal takes care of all, see if this can just be generalized
|
2015-01-02 13:51:15 +00:00
|
|
|
# Because this is an async task, the action handler is async. However,
|
|
|
|
# we need the 'normal' action handler for the status check, so get it
|
|
|
|
# now via the action_loader
|
2018-09-20 09:37:54 +00:00
|
|
|
async_handler = self._shared_loader_obj.action_loader.get(
|
|
|
|
'async_status',
|
2015-01-02 13:51:15 +00:00
|
|
|
task=async_task,
|
|
|
|
connection=self._connection,
|
2015-07-21 16:12:22 +00:00
|
|
|
play_context=self._play_context,
|
2015-01-15 07:13:45 +00:00
|
|
|
loader=self._loader,
|
2015-05-04 06:33:10 +00:00
|
|
|
templar=templar,
|
2015-05-02 04:48:11 +00:00
|
|
|
shared_loader_obj=self._shared_loader_obj,
|
2015-01-02 13:51:15 +00:00
|
|
|
)
|
|
|
|
|
2017-11-22 20:35:58 +00:00
|
|
|
time_left = self._task.async_val
|
2015-01-02 13:51:15 +00:00
|
|
|
while time_left > 0:
|
|
|
|
time.sleep(self._task.poll)
|
|
|
|
|
2016-09-27 17:31:40 +00:00
|
|
|
try:
|
2018-09-20 09:37:54 +00:00
|
|
|
async_result = async_handler.run(task_vars=task_vars)
|
2016-09-27 17:31:40 +00:00
|
|
|
# We do not bail out of the loop in cases where the failure
|
|
|
|
# is associated with a parsing error. The async_runner can
|
|
|
|
# have issues which result in a half-written/unparseable result
|
|
|
|
# file on disk, which manifests to the user as a timeout happening
|
|
|
|
# before it's time to timeout.
|
2017-03-23 01:50:28 +00:00
|
|
|
if (int(async_result.get('finished', 0)) == 1 or
|
|
|
|
('failed' in async_result and async_result.get('_ansible_parsed', False)) or
|
|
|
|
'skipped' in async_result):
|
2016-09-27 17:31:40 +00:00
|
|
|
break
|
|
|
|
except Exception as e:
|
|
|
|
# Connections can raise exceptions during polling (eg, network bounce, reboot); these should be non-fatal.
|
2017-03-23 01:50:28 +00:00
|
|
|
# On an exception, call the connection's reset method if it has one
|
|
|
|
# (eg, drop/recreate WinRM connection; some reused connections are in a broken state)
|
2016-09-27 17:31:40 +00:00
|
|
|
display.vvvv("Exception during async poll, retrying... (%s)" % to_text(e))
|
|
|
|
display.debug("Async poll exception was:\n%s" % to_text(traceback.format_exc()))
|
|
|
|
try:
|
2018-09-20 09:37:54 +00:00
|
|
|
async_handler._connection.reset()
|
2016-09-27 17:31:40 +00:00
|
|
|
except AttributeError:
|
|
|
|
pass
|
2015-01-02 13:51:15 +00:00
|
|
|
|
2018-02-08 06:57:16 +00:00
|
|
|
# Little hack to raise the exception if we've exhausted the timeout period
|
|
|
|
time_left -= self._task.poll
|
|
|
|
if time_left <= 0:
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
time_left -= self._task.poll
|
2015-01-02 13:51:15 +00:00
|
|
|
|
|
|
|
if int(async_result.get('finished', 0)) != 1:
|
2016-08-16 15:59:30 +00:00
|
|
|
if async_result.get('_ansible_parsed'):
|
2018-11-20 05:01:17 +00:00
|
|
|
return dict(failed=True, msg="async task did not complete within the requested time - %ss" % self._task.async_val)
|
2016-07-06 21:26:50 +00:00
|
|
|
else:
|
|
|
|
return dict(failed=True, msg="async task produced unparseable results", async_result=async_result)
|
2015-01-02 13:51:15 +00:00
|
|
|
else:
|
|
|
|
return async_result
|
|
|
|
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
def _get_become(self, name):
|
|
|
|
become = become_loader.get(name)
|
|
|
|
if not become:
|
|
|
|
raise AnsibleError("Invalid become method specified, could not find matching plugin: '%s'. "
|
|
|
|
"Use `ansible-doc -t become -l` to list available plugins." % name)
|
|
|
|
return become
|
|
|
|
|
2015-10-02 04:35:22 +00:00
|
|
|
def _get_connection(self, variables, templar):
|
2014-11-14 22:14:08 +00:00
|
|
|
'''
|
|
|
|
Reads the connection property for the host, and returns the
|
|
|
|
correct connection object from the list of connection plugins
|
|
|
|
'''
|
|
|
|
|
2015-02-09 22:54:44 +00:00
|
|
|
if self._task.delegate_to is not None:
|
2015-09-18 18:48:26 +00:00
|
|
|
# since we're delegating, we don't want to use interpreter values
|
|
|
|
# which would have been set for the original target host
|
2016-08-24 14:52:36 +00:00
|
|
|
for i in list(variables.keys()):
|
2016-05-05 15:14:11 +00:00
|
|
|
if isinstance(i, string_types) and i.startswith('ansible_') and i.endswith('_interpreter'):
|
2015-09-18 18:48:26 +00:00
|
|
|
del variables[i]
|
|
|
|
# now replace the interpreter values with those that may have come
|
|
|
|
# from the delegated-to host
|
2015-10-21 15:05:45 +00:00
|
|
|
delegated_vars = variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict())
|
2015-09-18 18:48:26 +00:00
|
|
|
if isinstance(delegated_vars, dict):
|
|
|
|
for i in delegated_vars:
|
2016-05-05 15:14:11 +00:00
|
|
|
if isinstance(i, string_types) and i.startswith("ansible_") and i.endswith("_interpreter"):
|
2015-09-18 18:48:26 +00:00
|
|
|
variables[i] = delegated_vars[i]
|
2015-02-09 22:54:44 +00:00
|
|
|
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
# load connection
|
2017-11-09 20:04:40 +00:00
|
|
|
conn_type = self._play_context.connection
|
2018-04-26 18:17:16 +00:00
|
|
|
connection = self._shared_loader_obj.connection_loader.get(
|
|
|
|
conn_type,
|
|
|
|
self._play_context,
|
|
|
|
self._new_stdin,
|
|
|
|
task_uuid=self._task._uuid,
|
|
|
|
ansible_playbook_pid=to_text(os.getppid())
|
|
|
|
)
|
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
if not connection:
|
|
|
|
raise AnsibleError("the connection plugin '%s' was not found" % conn_type)
|
2015-10-02 04:35:22 +00:00
|
|
|
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
# load become plugin if needed
|
|
|
|
become_plugin = None
|
|
|
|
if self._play_context.become:
|
|
|
|
become_plugin = self._get_become(self._play_context.become_method)
|
|
|
|
|
|
|
|
if getattr(become_plugin, 'require_tty', False) and not getattr(connection, 'has_tty', False):
|
|
|
|
raise AnsibleError(
|
|
|
|
"The '%s' connection does not provide a tty which is requied for the selected "
|
|
|
|
"become plugin: %s." % (conn_type, become_plugin.name)
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
connection.set_become_plugin(become_plugin)
|
|
|
|
except AttributeError:
|
|
|
|
# Connection plugin does not support set_become_plugin
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Backwards compat for connection plugins that don't support become plugins
|
|
|
|
# Just do this unconditionally for now, we could move it inside of the
|
|
|
|
# AttributeError above later
|
|
|
|
self._play_context.set_become_plugin(become_plugin)
|
|
|
|
|
2017-11-16 18:49:57 +00:00
|
|
|
# FIXME: remove once all plugins pull all data from self._options
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
self._play_context.set_attributes_from_plugin(connection)
|
2017-08-20 15:20:30 +00:00
|
|
|
|
2017-11-09 20:04:40 +00:00
|
|
|
if any(((connection.supports_persistence and C.USE_PERSISTENT_CONNECTIONS), connection.force_persistence)):
|
2018-05-16 12:59:01 +00:00
|
|
|
self._play_context.timeout = connection.get_option('persistent_command_timeout')
|
2017-11-09 20:04:40 +00:00
|
|
|
display.vvvv('attempting to start connection', host=self._play_context.remote_addr)
|
|
|
|
display.vvvv('using connection plugin %s' % connection.transport, host=self._play_context.remote_addr)
|
2018-09-20 13:56:43 +00:00
|
|
|
|
|
|
|
options = self._get_persistent_connection_options(connection, variables, templar)
|
|
|
|
socket_path = self._start_connection(options)
|
2017-11-09 20:04:40 +00:00
|
|
|
display.vvvv('local domain socket path is %s' % socket_path, host=self._play_context.remote_addr)
|
|
|
|
setattr(connection, '_socket_path', socket_path)
|
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
return connection
|
|
|
|
|
2018-09-20 13:56:43 +00:00
|
|
|
def _get_persistent_connection_options(self, connection, variables, templar):
|
|
|
|
final_vars = combine_vars(variables, variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict()))
|
|
|
|
|
|
|
|
option_vars = C.config.get_plugin_vars('connection', connection._load_name)
|
2018-12-11 21:26:59 +00:00
|
|
|
plugin = connection._sub_plugin
|
|
|
|
if plugin['type'] != 'external':
|
|
|
|
option_vars.extend(C.config.get_plugin_vars(plugin['type'], plugin['name']))
|
2018-09-20 13:56:43 +00:00
|
|
|
|
|
|
|
options = {}
|
|
|
|
for k in option_vars:
|
|
|
|
if k in final_vars:
|
|
|
|
options[k] = templar.template(final_vars[k])
|
|
|
|
|
|
|
|
return options
|
|
|
|
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
def _set_plugin_options(self, plugin_type, variables, templar, task_keys):
|
|
|
|
try:
|
|
|
|
plugin = getattr(self._connection, '_%s' % plugin_type)
|
|
|
|
except AttributeError:
|
|
|
|
# Some plugins are assigned to private attrs, ``become`` is not
|
|
|
|
plugin = getattr(self._connection, plugin_type)
|
|
|
|
option_vars = C.config.get_plugin_vars(plugin_type, plugin._load_name)
|
|
|
|
options = {}
|
|
|
|
for k in option_vars:
|
|
|
|
if k in variables:
|
|
|
|
options[k] = templar.template(variables[k])
|
|
|
|
# TODO move to task method?
|
|
|
|
plugin.set_options(task_keys=task_keys, var_options=options)
|
|
|
|
|
2017-11-16 18:49:57 +00:00
|
|
|
def _set_connection_options(self, variables, templar):
|
|
|
|
|
2018-04-16 21:16:57 +00:00
|
|
|
# Keep the pre-delegate values for these keys
|
|
|
|
PRESERVE_ORIG = ('inventory_hostname',)
|
|
|
|
|
2017-11-16 18:49:57 +00:00
|
|
|
# create copy with delegation built in
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
final_vars = combine_vars(
|
|
|
|
variables,
|
|
|
|
variables.get('ansible_delegated_vars', {}).get(self._task.delegate_to, {})
|
|
|
|
)
|
2017-11-16 18:49:57 +00:00
|
|
|
|
|
|
|
# grab list of usable vars for this plugin
|
|
|
|
option_vars = C.config.get_plugin_vars('connection', self._connection._load_name)
|
|
|
|
|
|
|
|
# create dict of 'templated vars'
|
|
|
|
options = {'_extras': {}}
|
|
|
|
for k in option_vars:
|
2018-04-16 21:16:57 +00:00
|
|
|
if k in PRESERVE_ORIG:
|
|
|
|
options[k] = templar.template(variables[k])
|
|
|
|
elif k in final_vars:
|
2017-11-16 18:49:57 +00:00
|
|
|
options[k] = templar.template(final_vars[k])
|
|
|
|
|
|
|
|
# add extras if plugin supports them
|
|
|
|
if getattr(self._connection, 'allow_extras', False):
|
|
|
|
for k in final_vars:
|
|
|
|
if k.startswith('ansible_%s_' % self._connection._load_name) and k not in options:
|
|
|
|
options['_extras'][k] = templar.template(final_vars[k])
|
|
|
|
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
task_keys = self._task.dump_attrs()
|
2018-01-16 05:15:04 +00:00
|
|
|
|
Become plugins (#50991)
* [WIP] become plugins
Move from hardcoded method to plugins for ease of use, expansion and overrides
- load into connection as it is going to be the main consumer
- play_context will also use to keep backwards compat API
- ensure shell is used to construct commands when needed
- migrate settings remove from base config in favor of plugin specific configs
- cleanup ansible-doc
- add become plugin docs
- remove deprecated sudo/su code and keywords
- adjust become options for cli
- set plugin options from context
- ensure config defs are avaialbe before instance
- refactored getting the shell plugin, fixed tests
- changed into regex as they were string matching, which does not work with random string generation
- explicitly set flags for play context tests
- moved plugin loading up front
- now loads for basedir also
- allow pyc/o for non m modules
- fixes to tests and some plugins
- migrate to play objects fro play_context
- simiplify gathering
- added utf8 headers
- moved option setting
- add fail msg to dzdo
- use tuple for multiple options on fail/missing
- fix relative plugin paths
- shift from play context to play
- all tasks already inherit this from play directly
- remove obsolete 'set play'
- correct environment handling
- add wrap_exe option to pfexec
- fix runas to noop
- fixed setting play context
- added password configs
- removed required false
- remove from doc building till they are ready
future development:
- deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems
* cleanup
remove callers to removed func
removed --sudo cli doc refs
remove runas become_exe
ensure keyerorr on plugin
also fix backwards compat, missing method is attributeerror, not ansible error
get remote_user consistently
ignore missing system_tmpdirs on plugin load
correct config precedence
add deprecation
fix networking imports
backwards compat for plugins using BECOME_METHODS
* Port become_plugins to context.CLIARGS
This is a work in progress:
* Stop passing options around everywhere as we can use context.CLIARGS
instead
* Refactor make_become_commands as asked for by alikins
* Typo in comment fix
* Stop loading values from the cli in more than one place
Both play and play_context were saving default values from the cli
arguments directly. This changes things so that the default values are
loaded into the play and then play_context takes them from there.
* Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH
As alikins said, all other plugin paths are named
DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that
should be done all at one time rather than piecemeal.
* One to throw away
This is a set of hacks to get setting FieldAttribute defaults to command
line args to work. It's not fully done yet.
After talking it over with sivel and jimi-c this should be done by
fixing FieldAttributeBase and _get_parent_attribute() calls to do the
right thing when there is a non-None default.
What we want to be able to do ideally is something like this:
class Base(FieldAttributeBase):
_check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check'])
class Play(Base):
# lambda so that we have a chance to parse the command line args
# before we get here. In the future we might be able to restructure
# this so that the cli parsing code runs before these classes are
# defined.
class Task(Base):
pass
And still have a playbook like this function:
---
- hosts:
tasks:
- command: whoami
check_mode: True
(The check_mode test that is added as a separate commit in this PR will
let you test variations on this case).
There's a few separate reasons that the code doesn't let us do this or
a non-ugly workaround for this as written right now. The fix that
jimi-c, sivel, and I talked about may let us do this or it may still
require a workaround (but less ugly) (having one class that has the
FieldAttributes with default values and one class that inherits from
that but just overrides the FieldAttributes which now have defaults)
* Revert "One to throw away"
This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064.
* Set FieldAttr defaults directly from CLIARGS
* Remove dead code
* Move timeout directly to PlayContext, it's never needed on Play
* just for backwards compat, add a static version of BECOME_METHODS to constants
* Make the become attr on the connection public, since it's used outside of the connection
* Logic fix
* Nuke connection testing if it supports specific become methods
* Remove unused vars
* Address rebase issues
* Fix path encoding issue
* Remove unused import
* Various cleanups
* Restore network_cli check in _low_level_execute_command
* type improvements for cliargs_deferred_get and swap shallowcopy to default to False
* minor cleanups
* Allow the su plugin to work, since it doesn't define a prompt the same way
* Fix up ksu become plugin
* Only set prompt if build_become_command was called
* Add helper to assist connection plugins in knowing they need to wait for a prompt
* Fix tests and code expectations
* Doc updates
* Various additional minor cleanups
* Make doas functional
* Don't change connection signature, load become plugin from TaskExecutor
* Remove unused imports
* Add comment about setting the become plugin on the playcontext
* Fix up tests for recent changes
* Support 'Password:' natively for the doas plugin
* Make default prompts raw
* wording cleanups. ci_complete
* Remove unrelated changes
* Address spelling mistake
* Restore removed test, and udpate to use new functionality
* Add changelog fragment
* Don't hard fail in set_attributes_from_cli on missing CLI keys
* Remove unrelated change to loader
* Remove internal deprecated FieldAttributes now
* Emit deprecation warnings now
2019-02-11 17:27:44 +00:00
|
|
|
# set options with 'templated vars' specific to this plugin and dependant ones
|
|
|
|
self._connection.set_options(task_keys=task_keys, var_options=options)
|
|
|
|
self._set_plugin_options('shell', final_vars, templar, task_keys)
|
|
|
|
|
|
|
|
if self._connection.become is not None:
|
|
|
|
# FIXME: find alternate route to provide passwords,
|
|
|
|
# keep out of play objects to avoid accidental disclosure
|
|
|
|
task_keys['become_pass'] = self._play_context.become_pass
|
|
|
|
self._set_plugin_options('become', final_vars, templar, task_keys)
|
|
|
|
|
|
|
|
# FOR BACKWARDS COMPAT:
|
|
|
|
for option in ('become_user', 'become_flags', 'become_exe'):
|
|
|
|
try:
|
|
|
|
setattr(self._play_context, option, self._connection.become.get_option(option))
|
|
|
|
except KeyError:
|
|
|
|
pass # some plugins don't support all base flags
|
|
|
|
self._play_context.prompt = self._connection.become.prompt
|
2017-11-16 18:49:57 +00:00
|
|
|
|
2015-05-04 06:33:10 +00:00
|
|
|
def _get_action_handler(self, connection, templar):
|
2014-11-14 22:14:08 +00:00
|
|
|
'''
|
|
|
|
Returns the correct action plugin to handle the requestion task action
|
|
|
|
'''
|
2014-10-15 22:53:43 +00:00
|
|
|
|
2017-02-09 19:26:09 +00:00
|
|
|
module_prefix = self._task.action.split('_')[0]
|
|
|
|
|
2016-12-14 17:52:18 +00:00
|
|
|
# let action plugin override module, fallback to 'normal' action plugin otherwise
|
2015-08-28 20:32:09 +00:00
|
|
|
if self._task.action in self._shared_loader_obj.action_loader:
|
2014-11-14 22:14:08 +00:00
|
|
|
handler_name = self._task.action
|
2017-03-10 02:49:02 +00:00
|
|
|
elif all((module_prefix in C.NETWORK_GROUP_MODULES, module_prefix in self._shared_loader_obj.action_loader)):
|
2017-02-09 19:26:09 +00:00
|
|
|
handler_name = module_prefix
|
2016-12-14 17:52:18 +00:00
|
|
|
else:
|
2017-02-15 17:46:30 +00:00
|
|
|
handler_name = 'normal'
|
2014-10-15 22:53:43 +00:00
|
|
|
|
2015-08-28 20:32:09 +00:00
|
|
|
handler = self._shared_loader_obj.action_loader.get(
|
2014-11-14 22:14:08 +00:00
|
|
|
handler_name,
|
|
|
|
task=self._task,
|
|
|
|
connection=connection,
|
2015-07-21 16:12:22 +00:00
|
|
|
play_context=self._play_context,
|
2015-01-15 07:13:45 +00:00
|
|
|
loader=self._loader,
|
2015-05-04 06:33:10 +00:00
|
|
|
templar=templar,
|
2015-05-02 04:48:11 +00:00
|
|
|
shared_loader_obj=self._shared_loader_obj,
|
2014-11-14 22:14:08 +00:00
|
|
|
)
|
2015-03-02 18:38:45 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
if not handler:
|
|
|
|
raise AnsibleError("the handler '%s' was not found" % handler_name)
|
2014-10-15 22:53:43 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
return handler
|
2017-11-09 20:04:40 +00:00
|
|
|
|
2018-05-21 14:58:35 +00:00
|
|
|
def _start_connection(self, variables):
|
2017-11-09 20:04:40 +00:00
|
|
|
'''
|
|
|
|
Starts the persistent connection
|
|
|
|
'''
|
2018-10-01 18:29:59 +00:00
|
|
|
candidate_paths = [C.ANSIBLE_CONNECTION_PATH or os.path.dirname(sys.argv[0])]
|
|
|
|
candidate_paths.extend(os.environ['PATH'].split(os.pathsep))
|
|
|
|
for dirname in candidate_paths:
|
|
|
|
ansible_connection = os.path.join(dirname, 'ansible-connection')
|
|
|
|
if os.path.isfile(ansible_connection):
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise AnsibleError("Unable to find location of 'ansible-connection'. "
|
|
|
|
"Please set or check the value of ANSIBLE_CONNECTION_PATH")
|
2018-03-05 14:12:01 +00:00
|
|
|
|
|
|
|
python = sys.executable
|
2018-10-01 18:29:59 +00:00
|
|
|
master, slave = pty.openpty()
|
2018-03-26 16:49:30 +00:00
|
|
|
p = subprocess.Popen(
|
2018-10-01 18:29:59 +00:00
|
|
|
[python, ansible_connection, to_text(os.getppid())],
|
2018-03-26 16:49:30 +00:00
|
|
|
stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
|
|
)
|
2017-11-09 20:04:40 +00:00
|
|
|
os.close(slave)
|
|
|
|
|
2018-08-10 13:26:58 +00:00
|
|
|
# We need to set the pty into noncanonical mode. This ensures that we
|
|
|
|
# can receive lines longer than 4095 characters (plus newline) without
|
|
|
|
# truncating.
|
|
|
|
old = termios.tcgetattr(master)
|
|
|
|
new = termios.tcgetattr(master)
|
|
|
|
new[3] = new[3] & ~termios.ICANON
|
|
|
|
|
|
|
|
try:
|
|
|
|
termios.tcsetattr(master, termios.TCSANOW, new)
|
|
|
|
write_to_file_descriptor(master, variables)
|
|
|
|
write_to_file_descriptor(master, self._play_context.serialize())
|
|
|
|
|
|
|
|
(stdout, stderr) = p.communicate()
|
|
|
|
finally:
|
|
|
|
termios.tcsetattr(master, termios.TCSANOW, old)
|
|
|
|
os.close(master)
|
2017-11-09 20:04:40 +00:00
|
|
|
|
|
|
|
if p.returncode == 0:
|
2018-03-05 14:12:01 +00:00
|
|
|
result = json.loads(to_text(stdout, errors='surrogate_then_replace'))
|
2017-11-09 20:04:40 +00:00
|
|
|
else:
|
2018-03-05 14:12:01 +00:00
|
|
|
try:
|
|
|
|
result = json.loads(to_text(stderr, errors='surrogate_then_replace'))
|
2018-04-06 20:28:39 +00:00
|
|
|
except getattr(json.decoder, 'JSONDecodeError', ValueError):
|
|
|
|
# JSONDecodeError only available on Python 3.5+
|
2018-03-05 14:12:01 +00:00
|
|
|
result = {'error': to_text(stderr, errors='surrogate_then_replace')}
|
2017-11-09 20:04:40 +00:00
|
|
|
|
|
|
|
if 'messages' in result:
|
2018-12-19 15:54:42 +00:00
|
|
|
for level, message in result['messages']:
|
|
|
|
if level == 'log':
|
|
|
|
display.display(message, log_only=True)
|
|
|
|
elif level in ('debug', 'v', 'vv', 'vvv', 'vvvv', 'vvvvv', 'vvvvvv'):
|
|
|
|
getattr(display, level)(message, host=self._play_context.remote_addr)
|
|
|
|
else:
|
|
|
|
if hasattr(display, level):
|
|
|
|
getattr(display, level)(message)
|
|
|
|
else:
|
|
|
|
display.vvvv(message, host=self._play_context.remote_addr)
|
2017-11-09 20:04:40 +00:00
|
|
|
|
|
|
|
if 'error' in result:
|
|
|
|
if self._play_context.verbosity > 2:
|
2018-03-05 14:12:01 +00:00
|
|
|
if result.get('exception'):
|
|
|
|
msg = "The full traceback is:\n" + result['exception']
|
|
|
|
display.display(msg, color=C.COLOR_ERROR)
|
2017-11-09 20:04:40 +00:00
|
|
|
raise AnsibleError(result['error'])
|
|
|
|
|
|
|
|
return result['socket_path']
|