Add "only_if" capability, which allows task steps to be skipped if they do not match a conditional.

This commit is contained in:
Michael DeHaan 2012-03-20 19:55:04 -04:00
parent 149cc57b0f
commit dfbe591cc0
3 changed files with 42 additions and 13 deletions

View file

@ -77,6 +77,7 @@ class PlayBook(object):
self.changed = {}
self.invocations = {}
self.failures = {}
self.skipped = {}
# playbook file can be passed in as a path or
# as file contents (to support API usage)
@ -158,8 +159,13 @@ class PlayBook(object):
'resources' : self.invocations.get(host, 0),
'changed' : self.changed.get(host, 0),
'dark' : self.dark.get(host, 0),
'failed' : self.failures.get(host, 0)
'failed' : self.failures.get(host, 0),
'skipped' : self.skipped.get(host, 0)
}
# FIXME: TODO: use callback to reinstate per-host summary
# and add corresponding code in /bin/ansible-playbook
# print results
return results
def _prune_failed_hosts(self, host_list):
@ -175,12 +181,12 @@ class PlayBook(object):
for (host, res) in results['contacted'].iteritems():
# FIXME: make polling pattern in /bin/ansible match
# move to common function in utils
if not 'finished' in res and 'started' in res:
if not 'finished' in res and not 'skipped' in res and 'started' in res:
hosts.append(host)
return hosts
def _async_poll(self, runner, async_seconds, async_poll_interval):
def _async_poll(self, runner, async_seconds, async_poll_interval, only_if):
''' launch an async job, if poll_interval is set, wait for completion '''
# TODO: refactor this function
@ -197,6 +203,8 @@ class PlayBook(object):
if 'failed' in host_result:
self.callbacks.on_failed(host, host_result)
self.failures[host] = 1
if 'skipped' in host_result:
self.skipped[host] = self.skipped.get(host, 0) + 1
if async_poll_interval <= 0:
# if not polling, playbook requested fire and forget
@ -248,6 +256,11 @@ class PlayBook(object):
if 'failed' in host_result:
self.callbacks.on_failed(host, host_result)
self.failures[host] = 1
if 'skipped' in host_result:
# NOTE: callbacks on skipped? should not really
# happen at this point in the loop
self.skipped[host] = self.skipped.get(host, 0) + 1
for (host, host_result) in poll_results['contacted'].iteritems():
results['contacted'][host] = host_result
@ -267,7 +280,7 @@ class PlayBook(object):
return results
def _run_module(self, pattern, module, args, hosts, remote_user,
async_seconds, async_poll_interval):
async_seconds, async_poll_interval, only_if):
''' run a particular module step in a playbook '''
runner = ansible.runner.Runner(
@ -281,13 +294,14 @@ class PlayBook(object):
timeout=self.timeout,
remote_user=remote_user,
setup_cache=SETUP_CACHE,
basedir=self.basedir
basedir=self.basedir,
conditionally_execute_if=only_if
)
if async_seconds == 0:
rc = runner.run()
else:
rc = self._async_poll(runner, async_seconds, async_poll_interval)
rc = self._async_poll(runner, async_seconds, async_poll_interval, only_if)
dark_hosts = rc.get('dark',{})
for (host, error) in dark_hosts.iteritems():
@ -314,13 +328,12 @@ class PlayBook(object):
host_list = self._prune_failed_hosts(host_list)
# load the module name and parameters from the task entry
name = task['name']
action = task['action']
name = task['name'] # FIXME: error if not set
action = task['action'] # FIXME: error if not set
only_if = task.get('only_if', 'True')
async_seconds = int(task.get('async', 0)) # not async by default
async_poll_interval = int(task.get('poll', 10)) # default poll = 10 seconds
# comment = task.get('comment', '')
tokens = shlex.split(action)
module_name = tokens[0]
module_args = tokens[1:]
@ -336,7 +349,7 @@ class PlayBook(object):
# run the task in parallel
results = self._run_module(pattern, module_name,
module_args, host_list, remote_user,
async_seconds, async_poll_interval)
async_seconds, async_poll_interval, only_if)
# if no hosts are matched, carry on, unlike /bin/ansible
# which would warn you about this
@ -358,6 +371,7 @@ class PlayBook(object):
else:
self.dark[host] = self.dark[host] + 1
# FIXME: refactor
for host, results in contacted.iteritems():
self.processed[host] = 1
@ -378,6 +392,12 @@ class PlayBook(object):
self.changed[host] = 1
else:
self.changed[host] = self.changed[host] + 1
# TODO: verify/test that async steps are skippable
if results.get('skipped', False):
if not host in self.changed:
self.skipped[host] = 1
else:
self.skipped[host] = self.skipped[host] + 1
# flag which notify handlers need to be run
# this will be on a SUBSET of the actual host list. For instance

View file

@ -76,6 +76,7 @@ class Runner(object):
basedir=None,
setup_cache=None,
transport='paramiko',
conditionally_execute_if='True',
verbose=False):
'''
@ -95,6 +96,7 @@ class Runner(object):
if setup_cache is None:
setup_cache = {}
self.setup_cache = setup_cache
self.conditionally_execute_if = conditionally_execute_if
self.host_list, self.groups = self.parse_hosts(host_list)
self.module_path = module_path
@ -286,6 +288,7 @@ class Runner(object):
modifies the command using setup_cache variables (see playbook)
'''
args = module_args
if type(args) == list:
args = " ".join([ str(x) for x in module_args ])
@ -293,6 +296,11 @@ class Runner(object):
# by default the args to substitute in the action line are those from the setup cache
inject_vars = self.setup_cache.get(conn.host,{})
# see if we really need to run this or not...
conditional = utils.template(self.conditionally_execute_if, inject_vars)
if not eval(conditional):
return utils.smjson(dict(skipped=True))
# if the host file was an external script, execute it with the hostname
# as a first parameter to get the variables to use for the host
inject2 = {}

View file

@ -235,7 +235,8 @@
"changed": 2,
"dark": 0,
"failed": 0,
"resources": 9
"resources": 9,
"skipped": 0
}
}
}