diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index 7d7c7da582..65ef94958b 100644 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -189,7 +189,7 @@ class AggregateStats(object): for (host, value) in runner_results.get('contacted', {}).iteritems(): if not ignore_errors and (('failed' in value and bool(value['failed'])) or - ('rc' in value and value['rc'] != 0)): + ('failed_when_result' in value and [value['failed_when_result']] or ['rc' in value and value['rc'] != 0])[0]): self._increment('failures', host) elif 'skipped' in value and bool(value['skipped']): self._increment('skipped', host) diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index ba7599772e..584052ec34 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -29,7 +29,7 @@ class Task(object): 'delegate_to', 'first_available_file', 'ignore_errors', 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass', 'items_lookup_plugin', 'items_lookup_terms', 'environment', 'args', - 'any_errors_fatal', 'changed_when', 'always_run' + 'any_errors_fatal', 'changed_when', 'failed_when', 'always_run' ] # to prevent typos and such @@ -38,7 +38,7 @@ class Task(object): 'first_available_file', 'include', 'tags', 'register', 'ignore_errors', 'delegate_to', 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass', 'when', 'connection', 'environment', 'args', - 'any_errors_fatal', 'changed_when', 'always_run' + 'any_errors_fatal', 'changed_when', 'failed_when', 'always_run' ] def __init__(self, play, ds, module_vars=None, default_vars=None, additional_conditions=None): @@ -88,7 +88,7 @@ class Task(object): else: raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name)) - elif x in [ 'changed_when', 'when']: + elif x in [ 'changed_when', 'failed_when', 'when']: ds[x] = "jinja2_compare %s" % (ds[x]) elif x.startswith("when_"): if 'when' in ds: @@ -167,6 +167,11 @@ class Task(object): if self.changed_when is not None: self.changed_when = utils.compile_when_to_only_if(self.changed_when) + self.failed_when = ds.get('failed_when', None) + + if self.failed_when is not None: + self.failed_when = utils.compile_when_to_only_if(self.failed_when) + self.async_seconds = int(ds.get('async', 0)) # not async by default self.async_poll_interval = int(ds.get('poll', 10)) # default poll = 10 seconds self.notify = ds.get('notify', []) @@ -223,6 +228,7 @@ class Task(object): self.module_vars['ignore_errors'] = self.ignore_errors self.module_vars['register'] = self.register self.module_vars['changed_when'] = self.changed_when + self.module_vars['failed_when'] = self.failed_when self.module_vars['always_run'] = self.always_run # tags allow certain parts of a playbook to be run without running the whole playbook diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index e676d8fa50..f78bcfc07e 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -507,7 +507,7 @@ class Runner(object): for x in results: if x.get('changed') == True: all_changed = True - if (x.get('failed') == True) or (('rc' in x) and (x['rc'] != 0)): + if (x.get('failed') == True) or ('failed_when_result' in x and [x['failed_when_result']] or [('rc' in x) and (x['rc'] != 0)])[0]: all_failed = True break msg = 'All items completed' @@ -669,13 +669,17 @@ class Runner(object): ) changed_when = self.module_vars.get('changed_when') - if changed_when is not None: + failed_when = self.module_vars.get('failed_when') + if changed_when is not None or failed_when is not None: register = self.module_vars.get('register') if register is not None: if 'stdout' in data: data['stdout_lines'] = data['stdout'].splitlines() inject[register] = data - data['changed'] = utils.check_conditional(changed_when, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars) + if changed_when is not None: + data['changed'] = utils.check_conditional(changed_when, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars) + if failed_when is not None: + data['failed_when_result'] = data['failed'] = utils.check_conditional(failed_when, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars) if is_chained: # no callbacks diff --git a/lib/ansible/runner/return_data.py b/lib/ansible/runner/return_data.py index 764bcc870b..5e4933fefb 100644 --- a/lib/ansible/runner/return_data.py +++ b/lib/ansible/runner/return_data.py @@ -59,5 +59,5 @@ class ReturnData(object): return self.comm_ok def is_successful(self): - return self.comm_ok and (self.result.get('failed', False) == False) and (self.result.get('rc',0) == 0) + return self.comm_ok and (self.result.get('failed', False) == False) and ('failed_when_result' in self.result and [not self.result['failed_when_result']] or [self.result.get('rc',0) == 0])[0] diff --git a/test/TestPlayBook.py b/test/TestPlayBook.py index 10885e3789..971c67b15f 100644 --- a/test/TestPlayBook.py +++ b/test/TestPlayBook.py @@ -474,6 +474,34 @@ class TestPlaybook(unittest.TestCase): assert utils.jsonify(expected, format=True) == utils.jsonify(actual,format=True) + def test_playbook_failed_when(self): + test_callbacks = TestCallbacks() + playbook = ansible.playbook.PlayBook( + playbook=os.path.join(self.test_dir, 'playbook-failed_when.yml'), + host_list='test/ansible_hosts', + stats=ans_callbacks.AggregateStats(), + callbacks=test_callbacks, + runner_callbacks=test_callbacks + ) + actual = playbook.run() + + # if different, this will output to screen + print "**ACTUAL**" + print utils.jsonify(actual, format=True) + expected = { + "localhost": { + "changed": 2, + "failures": 1, + "ok": 2, + "skipped": 0, + "unreachable": 0 + } + } + print "**EXPECTED**" + print utils.jsonify(expected, format=True) + + assert utils.jsonify(expected, format=True) == utils.jsonify(actual,format=True) + def test_playbook_always_run(self): test_callbacks = TestCallbacks() diff --git a/test/playbook-failed_when.yml b/test/playbook-failed_when.yml new file mode 100644 index 0000000000..4992ebe1fe --- /dev/null +++ b/test/playbook-failed_when.yml @@ -0,0 +1,15 @@ +--- +- hosts: all + connection: local + gather_facts: False + + tasks: + - action: shell exit 0 + register: exit + failed_when: not exit.rc in [0, 1] + - action: shell exit 1 + register: exit + failed_when: exit.rc not in [0, 1] + - action: shell exit 2 + register: exit + failed_when: exit.rc not in [0, 1]