Repository URL to install this package:
|
Version:
6.0.0 ▾
|
# Copyright: (c) 2020, Brian Scholer <@briantist>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import time
from ansible.errors import AnsibleError
from ansible.plugins.action import ActionBase
from ansible.playbook.task import Task
from ansible.utils.display import Display
display = Display()
def clean_async_result(reference_keys, obj):
for key in reference_keys:
obj.pop(key)
return obj
class ActionModule(ActionBase):
_default_async_timeout = 300
_default_async_poll = 1
def __init__(self, *args, **kwargs):
super(ActionModule, self).__init__(*args, **kwargs)
def run(self, tmp=None, task_vars=None):
self._supports_check_mode = True
self._supports_async = False
check_mode = self._task.check_mode
async_timeout = self._task.args.get('async_timeout', self._default_async_timeout)
async_poll = self._task.args.get('async_poll', self._default_async_poll)
result = super(ActionModule, self).run(tmp, task_vars)
if async_poll <= 0:
raise AnsibleError("The 'async_poll' option must be greater than 0, got: %i" % async_poll)
# build the wait_for_connection object for later use
wait_for_connection_task = self._task.copy()
wait_for_connection_task.args = {
'timeout': async_timeout,
'sleep': async_poll,
}
wait_connection_action = self._shared_loader_obj.action_loader.get(
'wait_for_connection',
task=wait_for_connection_task,
connection=self._connection,
play_context=self._play_context,
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj
)
# if it's not in check mode, call the module async so the WinRM restart doesn't kill ansible
if not check_mode:
self._task.async_val = async_timeout
self._task.poll = async_poll
result = status = self._execute_module(
task_vars=task_vars,
module_args=self._task.args
)
display.vvvv("Internal Async Result: %r" % status)
# if we're in check mode (not doing async) return the result now
if check_mode:
return result
# turn off async so we don't run the following actions as async
self._task.async_val = 0
# build the async_status object
async_status_load_params = dict(
action='async_status jid=%s' % status['ansible_job_id'],
environment=self._task.environment
)
async_status_task = Task().load(async_status_load_params)
async_status_action = self._shared_loader_obj.action_loader.get(
'async_status',
task=async_status_task,
connection=self._connection,
play_context=self._play_context,
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj
)
# build an async_status mode=cleanup object
async_cleanup_load_params = dict(
action='async_status mode=cleanup jid=%s' % status['ansible_job_id'],
environment=self._task.environment
)
async_cleanup_task = Task().load(async_cleanup_load_params)
async_cleanup_action = self._shared_loader_obj.action_loader.get(
'async_status',
task=async_cleanup_task,
connection=self._connection,
play_context=self._play_context,
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj
)
# Retries here is a fallback in case the module fails in an unexpected way
# which can sometimes not properly set the failed field in the return.
# It is not related to async retries.
# Without this, that situation would cause an infinite loop.
max_retries = 3
retries = 0
while not check_mode:
try:
# check up on the async job
job_status = async_status_action.run(task_vars=task_vars)
display.vvvv("Async Job Status: %r" % job_status)
if job_status.get('failed', False):
raise AnsibleError(job_status.get('msg', job_status))
if job_status.get('finished', False):
result = job_status
break
time.sleep(self._task.poll)
except BaseException as e:
retries += 1
if retries >= max_retries:
display.vvvv("Max retries reached.")
raise e
display.vvvv("Retrying (%s of %s)" % (retries, max_retries))
display.vvvv("Falling back to wait_for_connection: %r" % e)
wait_connection_action.run(task_vars=task_vars)
try:
# let's try to clean up after our implicit async
job_status = async_cleanup_action.run(task_vars=task_vars)
if job_status.get('failed', False):
display.vvvv("Clean up of async status failed on the remote host: %r" % job_status.get('msg', job_status))
except BaseException as e:
# let's swallow errors during implicit cleanup to aovid interrupting what was otherwise a successful run
display.vvvv("Clean up of async status failed on the remote host: %r" % e)
return clean_async_result(status.keys(), result)