diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index ce06af6609..f32a56ebbb 100644 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -147,7 +147,7 @@ class DefaultRunnerCallbacks(object): def __init__(self): pass - def on_failed(self, host, res): + def on_failed(self, host, res, ignore_errors=False): pass def on_ok(self, host, res): @@ -185,7 +185,7 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks): self.options = None self._async_notified = {} - def on_failed(self, host, res): + def on_failed(self, host, res, ignore_errors=False): self._on_any(host,res) @@ -259,7 +259,7 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): else: print "fatal: [%s] => %s" % (host, msg) - def on_failed(self, host, results): + def on_failed(self, host, results, ignore_errors=False): item = results.get('item', None) @@ -269,6 +269,8 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): msg = "failed: [%s] => %s" % (host, utils.jsonify(results)) print stringc(msg, 'red') + if ignore_errors: + print stringc("...ignoring", 'yellow') def on_ok(self, host, host_result): diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index c1ff016c68..1cdf450cde 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -23,13 +23,13 @@ class Task(object): __slots__ = [ 'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval', 'notify', 'module_name', 'module_args', 'module_vars', - 'play', 'notified_by', 'tags', 'with_items', 'first_available_file' + 'play', 'notified_by', 'tags', 'with_items', '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' + 'include', 'tags', 'ignore_errors' ] def __init__(self, play, ds, module_vars=None): @@ -62,7 +62,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.ignore_errors = ds.get('ignore_errors', False) + # notify can be a string or a list, store as a list if isinstance(self.notify, basestring): self.notify = [ self.notify ] @@ -97,6 +98,9 @@ class Task(object): self.with_items = [ ] self.module_vars['items'] = self.with_items + # make ignore_errors accessable to Runner code + self.module_vars['ignore_errors'] = self.ignore_errors + # tags allow certain parts of a playbook to be run without running the whole playbook apply_tags = ds.get('tags', None) if apply_tags is not None: diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 4468f107c1..9ab2fc6edb 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -602,7 +602,13 @@ class Runner(object): if 'skipped' in data: self.callbacks.on_skipped(result.host) elif not result.is_successful(): - self.callbacks.on_failed(result.host, data) + ignore_errors = self.module_vars.get('ignore_errors', False) + self.callbacks.on_failed(result.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) return result diff --git a/test/TestPlayBook.py b/test/TestPlayBook.py index 8e5704e7ee..cf13ee3852 100644 --- a/test/TestPlayBook.py +++ b/test/TestPlayBook.py @@ -54,8 +54,8 @@ class TestCallbacks(object): def on_unreachable(self, host, msg): EVENTS.append([ 'unreachable', [ host, msg ]]) - def on_failed(self, host, results): - EVENTS.append([ 'failed', [ host, results ]]) + def on_failed(self, host, results, ignore_errors): + EVENTS.append([ 'failed', [ host, results, ignore_errors ]]) def on_ok(self, host, result): # delete certain info from host_result to make test comparisons easier