Adds 'delegate_to' as a task option which can be used to signal load balancers and outage windows.
This commit is contained in:
parent
fda4f80828
commit
f07e55c568
3 changed files with 56 additions and 27 deletions
|
@ -39,6 +39,7 @@ Ansible Changes By Release
|
|||
* ANSIBLE_KEEP_REMOTE_FILES=1 can be used in debugging (envrionment variable)
|
||||
* add pattern= as a paramter to the service module
|
||||
* various fixes to mysql & postresql modules
|
||||
* adds 'delegate_to' for a task, which can be used to signal outage windows and load balancers on behalf of hosts
|
||||
|
||||
0.6 "Cabo" -- August 6, 2012
|
||||
|
||||
|
|
|
@ -23,13 +23,15 @@ class Task(object):
|
|||
__slots__ = [
|
||||
'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval',
|
||||
'notify', 'module_name', 'module_args', 'module_vars',
|
||||
'play', 'notified_by', 'tags', 'register', 'with_items', 'first_available_file', 'ignore_errors'
|
||||
'play', 'notified_by', 'tags', 'register', 'with_items',
|
||||
'delegate_to', 'first_available_file', 'ignore_errors'
|
||||
]
|
||||
|
||||
# to prevent typos and such
|
||||
VALID_KEYS = [
|
||||
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items', 'first_available_file',
|
||||
'include', 'tags', 'register', 'ignore_errors'
|
||||
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items',
|
||||
'first_available_file', 'include', 'tags', 'register', 'ignore_errors',
|
||||
'delegate_to'
|
||||
]
|
||||
|
||||
def __init__(self, play, ds, module_vars=None):
|
||||
|
@ -63,6 +65,8 @@ class Task(object):
|
|||
self.notify = ds.get('notify', [])
|
||||
self.first_available_file = ds.get('first_available_file', None)
|
||||
self.with_items = ds.get('with_items', None)
|
||||
self.delegate_to = ds.get('delegate_to', None)
|
||||
|
||||
self.ignore_errors = ds.get('ignore_errors', False)
|
||||
|
||||
# notify can be a string or a list, store as a list
|
||||
|
@ -99,6 +103,9 @@ class Task(object):
|
|||
self.with_items = [ ]
|
||||
self.module_vars['items'] = self.with_items
|
||||
|
||||
# allow runner to see delegate_to option
|
||||
self.module_vars['delegate_to'] = self.delegate_to
|
||||
|
||||
# make ignore_errors accessable to Runner code
|
||||
self.module_vars['ignore_errors'] = self.ignore_errors
|
||||
|
||||
|
|
|
@ -70,15 +70,25 @@ class ReturnData(object):
|
|||
|
||||
__slots__ = [ 'result', 'comm_ok', 'host' ]
|
||||
|
||||
def __init__(self, host=None, result=None, comm_ok=True):
|
||||
def __init__(self, conn=None, host=None, result=None, comm_ok=True):
|
||||
|
||||
# which host is this ReturnData about?
|
||||
if conn is not None:
|
||||
delegate_for = getattr(conn, '_delegate_for', None)
|
||||
if delegate_for:
|
||||
self.host = delegate_for
|
||||
else:
|
||||
self.host = conn.host
|
||||
else:
|
||||
self.host = host
|
||||
|
||||
self.result = result
|
||||
self.comm_ok = comm_ok
|
||||
|
||||
if type(self.result) in [ str, unicode ]:
|
||||
self.result = utils.parse_json(self.result)
|
||||
|
||||
if host is None:
|
||||
if self.host is None:
|
||||
raise Exception("host not set")
|
||||
if type(self.result) != dict:
|
||||
raise Exception("dictionary result expected")
|
||||
|
@ -236,13 +246,13 @@ class Runner(object):
|
|||
cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module]])
|
||||
|
||||
res = self._low_level_exec_command(conn, cmd, tmp, sudoable=True)
|
||||
return ReturnData(host=conn.host, result=res)
|
||||
return ReturnData(conn=conn, result=res)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _execute_raw(self, conn, tmp, inject=None):
|
||||
''' execute a non-module command for bootstrapping, or if there's no python on a device '''
|
||||
return ReturnData(host=conn.host, result=dict(
|
||||
return ReturnData(conn=conn, result=dict(
|
||||
stdout=self._low_level_exec_command(conn, self.module_args, tmp, sudoable = True)
|
||||
))
|
||||
|
||||
|
@ -292,7 +302,7 @@ class Runner(object):
|
|||
dest = options.get('dest', None)
|
||||
if (source is None and not 'first_available_file' in inject) or dest is None:
|
||||
result=dict(failed=True, msg="src and dest are required")
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# if we have first_available_file in our vars
|
||||
# look up the files and use the first one we find as src
|
||||
|
@ -306,7 +316,7 @@ class Runner(object):
|
|||
break
|
||||
if not found:
|
||||
results=dict(failed=True, msg="could not find src in first_available_file list")
|
||||
return ReturnData(host=conn.host, results=results)
|
||||
return ReturnData(conn=conn, results=results)
|
||||
|
||||
source = utils.template(source, inject)
|
||||
source = utils.path_dwim(self.basedir, source)
|
||||
|
@ -314,7 +324,7 @@ class Runner(object):
|
|||
local_md5 = utils.md5(source)
|
||||
if local_md5 is None:
|
||||
result=dict(failed=True, msg="could not find src=%s" % source)
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
remote_md5 = self._remote_md5(conn, tmp, dest)
|
||||
|
||||
|
@ -334,7 +344,7 @@ class Runner(object):
|
|||
else:
|
||||
# no need to transfer the file, already correct md5
|
||||
result = dict(changed=False, md5sum=remote_md5, transferred=False)
|
||||
return ReturnData(host=conn.host, result=result).daisychain('file')
|
||||
return ReturnData(conn=conn, result=result).daisychain('file')
|
||||
|
||||
# *****************************************************
|
||||
|
||||
|
@ -347,7 +357,7 @@ class Runner(object):
|
|||
dest = options.get('dest', None)
|
||||
if source is None or dest is None:
|
||||
results = dict(failed=True, msg="src and dest are required")
|
||||
return ReturnData(host=conn.host, result=results)
|
||||
return ReturnData(conn=conn, result=results)
|
||||
|
||||
# apply templating to source argument
|
||||
source = utils.template(source, inject)
|
||||
|
@ -365,13 +375,13 @@ class Runner(object):
|
|||
# but keep going to fetch other log files
|
||||
if remote_md5 == '0':
|
||||
result = dict(msg="unable to calculate the md5 sum of the remote file", file=source, changed=False)
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
if remote_md5 == '1':
|
||||
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
if remote_md5 == '2':
|
||||
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# calculate md5 sum for the local file
|
||||
local_md5 = utils.md5(dest)
|
||||
|
@ -386,12 +396,12 @@ class Runner(object):
|
|||
new_md5 = utils.md5(dest)
|
||||
if new_md5 != remote_md5:
|
||||
result = dict(failed=True, md5sum=new_md5, msg="md5 mismatch", file=source)
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
result = dict(changed=True, md5sum=new_md5)
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
else:
|
||||
result = dict(changed=False, md5sum=local_md5, file=source)
|
||||
return ReturnData(host=conn.host, result=result)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
|
@ -407,7 +417,7 @@ class Runner(object):
|
|||
dest = options.get('dest', None)
|
||||
if (source is None and 'first_available_file' not in inject) or dest is None:
|
||||
result = dict(failed=True, msg="src and dest are required")
|
||||
return ReturnData(host=conn.host, comm_ok=False, result=result)
|
||||
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||
|
||||
# if we have first_available_file in our vars
|
||||
# look up the files and use the first one we find as src
|
||||
|
@ -421,7 +431,7 @@ class Runner(object):
|
|||
break
|
||||
if not found:
|
||||
result = dict(failed=True, msg="could not find src in first_available_file list")
|
||||
return ReturnData(host=conn.host, comm_ok=False, result=result)
|
||||
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||
|
||||
source = utils.template(source, inject)
|
||||
|
||||
|
@ -430,7 +440,8 @@ class Runner(object):
|
|||
resultant = utils.template_from_file(self.basedir, source, inject)
|
||||
except Exception, e:
|
||||
result = dict(failed=True, msg=str(e))
|
||||
return ReturnData(host=conn.host, comm_ok=False, result=result)
|
||||
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||
|
||||
xfered = self._transfer_str(conn, tmp, 'source', resultant)
|
||||
|
||||
# run the copy module, queue the file module
|
||||
|
@ -483,8 +494,10 @@ class Runner(object):
|
|||
inject.update(self.module_vars)
|
||||
inject['hostvars'] = self.setup_cache
|
||||
|
||||
items = self.module_vars.get('items', [])
|
||||
# allow with_items to work in playbooks...
|
||||
# apt and yum are converted into a single call, others run in a loop
|
||||
|
||||
items = self.module_vars.get('items', [])
|
||||
if isinstance(items, basestring) and items.startswith("$"):
|
||||
items = items.replace("$","")
|
||||
if items in inject:
|
||||
|
@ -499,6 +512,8 @@ class Runner(object):
|
|||
inject['item'] = ",".join(items)
|
||||
items = []
|
||||
|
||||
# logic to decide how to run things depends on whether with_items is used
|
||||
|
||||
if len(items) == 0:
|
||||
return self._executor_internal_inner(host, inject, port)
|
||||
else:
|
||||
|
@ -570,8 +585,14 @@ class Runner(object):
|
|||
return ReturnData(host=host, result=result)
|
||||
|
||||
conn = None
|
||||
actual_host = host
|
||||
try:
|
||||
conn = self.connector.connect(host, port)
|
||||
delegate_to = inject.get('delegate_to', None)
|
||||
if delegate_to is not None:
|
||||
actual_host = delegate_to
|
||||
conn = self.connector.connect(actual_host, port)
|
||||
if delegate_to is not None:
|
||||
conn._delegate_for = host
|
||||
except errors.AnsibleConnectionFailed, e:
|
||||
result = dict(failed=True, msg="FAILED: %s" % str(e))
|
||||
return ReturnData(host=host, comm_ok=False, result=result)
|
||||
|
@ -627,17 +648,17 @@ class Runner(object):
|
|||
# no callbacks
|
||||
return result
|
||||
if 'skipped' in data:
|
||||
self.callbacks.on_skipped(result.host)
|
||||
self.callbacks.on_skipped(host)
|
||||
elif not result.is_successful():
|
||||
ignore_errors = self.module_vars.get('ignore_errors', False)
|
||||
self.callbacks.on_failed(result.host, data, ignore_errors)
|
||||
self.callbacks.on_failed(host, data, ignore_errors)
|
||||
if ignore_errors:
|
||||
if 'failed' in result.result:
|
||||
result.result['failed'] = False
|
||||
if 'rc' in result.result:
|
||||
result.result['rc'] = 0
|
||||
else:
|
||||
self.callbacks.on_ok(result.host, data)
|
||||
self.callbacks.on_ok(host, data)
|
||||
return result
|
||||
|
||||
# *****************************************************
|
||||
|
|
Loading…
Reference in a new issue