mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-24 13:50:22 -07:00
New keyword: ignore_unreachable (#43857)
This commit is contained in:
parent
2603604fd6
commit
653d9c0f87
15 changed files with 129 additions and 2 deletions
2
changelogs/fragments/ignore_unreachable.yml
Normal file
2
changelogs/fragments/ignore_unreachable.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
major_changes:
|
||||||
|
- New keyword `ignore_unreachable` for plays and blocks. Allows ignoring tasks that fail due to unreachable hosts, and check results with `is unreachable` test.
|
|
@ -37,6 +37,7 @@ gather_timeout: Allows you to set the timeout for the fact gathering plugin cont
|
||||||
handlers: "A section with tasks that are treated as handlers, these won't get executed normally, only when notified after each section of tasks is complete."
|
handlers: "A section with tasks that are treated as handlers, these won't get executed normally, only when notified after each section of tasks is complete."
|
||||||
hosts: "A list of groups, hosts or host pattern that translates into a list of hosts that are the play's target."
|
hosts: "A list of groups, hosts or host pattern that translates into a list of hosts that are the play's target."
|
||||||
ignore_errors: Boolean that allows you to ignore task failures and continue with play. It does not affect connection errors.
|
ignore_errors: Boolean that allows you to ignore task failures and continue with play. It does not affect connection errors.
|
||||||
|
ignore_unreachable: Boolean that allows you to ignore unreachable hosts and continue with play. This does not affect other task errors (see :term:`ignore_errors`) but is useful for groups of volatile/ephemeral hosts.
|
||||||
loop: "Takes a list for the task to iterate over, saving each list element into the ``item`` variable (configurable via loop_control)"
|
loop: "Takes a list for the task to iterate over, saving each list element into the ``item`` variable (configurable via loop_control)"
|
||||||
loop_control: |
|
loop_control: |
|
||||||
Several keys here allow you to modify/set loop behaviour in a task.
|
Several keys here allow you to modify/set loop behaviour in a task.
|
||||||
|
|
|
@ -574,6 +574,7 @@ class Base(FieldAttributeBase):
|
||||||
_no_log = FieldAttribute(isa='bool')
|
_no_log = FieldAttribute(isa='bool')
|
||||||
_run_once = FieldAttribute(isa='bool')
|
_run_once = FieldAttribute(isa='bool')
|
||||||
_ignore_errors = FieldAttribute(isa='bool')
|
_ignore_errors = FieldAttribute(isa='bool')
|
||||||
|
_ignore_unreachable = FieldAttribute(isa='bool')
|
||||||
_check_mode = FieldAttribute(isa='bool')
|
_check_mode = FieldAttribute(isa='bool')
|
||||||
_diff = FieldAttribute(isa='bool')
|
_diff = FieldAttribute(isa='bool')
|
||||||
_any_errors_fatal = FieldAttribute(isa='bool')
|
_any_errors_fatal = FieldAttribute(isa='bool')
|
||||||
|
|
|
@ -511,8 +511,13 @@ class StrategyBase:
|
||||||
self._tqm._stats.increment('changed', original_host.name)
|
self._tqm._stats.increment('changed', original_host.name)
|
||||||
self._tqm.send_callback('v2_runner_on_failed', task_result, ignore_errors=ignore_errors)
|
self._tqm.send_callback('v2_runner_on_failed', task_result, ignore_errors=ignore_errors)
|
||||||
elif task_result.is_unreachable():
|
elif task_result.is_unreachable():
|
||||||
self._tqm._unreachable_hosts[original_host.name] = True
|
ignore_unreachable = original_task.ignore_unreachable
|
||||||
iterator._play._removed_hosts.append(original_host.name)
|
if not ignore_unreachable:
|
||||||
|
self._tqm._unreachable_hosts[original_host.name] = True
|
||||||
|
iterator._play._removed_hosts.append(original_host.name)
|
||||||
|
else:
|
||||||
|
self._tqm._stats.increment('skipped', original_host.name)
|
||||||
|
task_result._result['skip_reason'] = 'Host %s is unreachable' % original_host.name
|
||||||
self._tqm._stats.increment('dark', original_host.name)
|
self._tqm._stats.increment('dark', original_host.name)
|
||||||
self._tqm.send_callback('v2_runner_on_unreachable', task_result)
|
self._tqm.send_callback('v2_runner_on_unreachable', task_result)
|
||||||
elif task_result.is_skipped():
|
elif task_result.is_skipped():
|
||||||
|
|
|
@ -45,6 +45,18 @@ def success(result):
|
||||||
return not failed(result)
|
return not failed(result)
|
||||||
|
|
||||||
|
|
||||||
|
def unreachable(result):
|
||||||
|
''' Test if task result yields unreachable '''
|
||||||
|
if not isinstance(result, MutableMapping):
|
||||||
|
raise errors.AnsibleFilterError("The 'unreachable' test expects a dictionary")
|
||||||
|
return result.get('unreachable', False)
|
||||||
|
|
||||||
|
|
||||||
|
def reachable(result):
|
||||||
|
''' Test if task result yields reachable '''
|
||||||
|
return not unreachable(result)
|
||||||
|
|
||||||
|
|
||||||
def changed(result):
|
def changed(result):
|
||||||
''' Test if task result yields changed '''
|
''' Test if task result yields changed '''
|
||||||
if not isinstance(result, MutableMapping):
|
if not isinstance(result, MutableMapping):
|
||||||
|
@ -150,6 +162,8 @@ class TestModule(object):
|
||||||
'succeeded': success,
|
'succeeded': success,
|
||||||
'success': success,
|
'success': success,
|
||||||
'successful': success,
|
'successful': success,
|
||||||
|
'reachable': reachable,
|
||||||
|
'unreachable': unreachable,
|
||||||
|
|
||||||
# changed testing
|
# changed testing
|
||||||
'changed': changed,
|
'changed': changed,
|
||||||
|
|
1
test/integration/targets/ignore_unreachable/aliases
Normal file
1
test/integration/targets/ignore_unreachable/aliases
Normal file
|
@ -0,0 +1 @@
|
||||||
|
shippable/posix/group3
|
|
@ -0,0 +1,11 @@
|
||||||
|
import ansible.plugins.connection.local as ansible_local
|
||||||
|
from ansible.errors import AnsibleConnectionFailure
|
||||||
|
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(ansible_local.Connection):
|
||||||
|
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||||
|
display.debug('Intercepted call to exec remote command')
|
||||||
|
raise AnsibleConnectionFailure('BADLOCAL Error: this is supposed to fail')
|
|
@ -0,0 +1,11 @@
|
||||||
|
import ansible.plugins.connection.local as ansible_local
|
||||||
|
from ansible.errors import AnsibleConnectionFailure
|
||||||
|
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(ansible_local.Connection):
|
||||||
|
def put_file(self, in_path, out_path):
|
||||||
|
display.debug('Intercepted call to send data')
|
||||||
|
raise AnsibleConnectionFailure('BADLOCAL Error: this is supposed to fail')
|
3
test/integration/targets/ignore_unreachable/inventory
Normal file
3
test/integration/targets/ignore_unreachable/inventory
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
nonexistent ansible_host=169.254.199.200
|
||||||
|
bad_put_file ansible_host=localhost ansible_connection=bad_put_file
|
||||||
|
bad_exec ansible_host=localhost ansible_connection=bad_exec
|
|
@ -0,0 +1,2 @@
|
||||||
|
dependencies:
|
||||||
|
- prepare_tests
|
16
test/integration/targets/ignore_unreachable/runme.sh
Executable file
16
test/integration/targets/ignore_unreachable/runme.sh
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
export ANSIBLE_CONNECTION_PLUGINS=./fake_connectors
|
||||||
|
# use fake connectors that raise srrors at different stages
|
||||||
|
ansible-playbook test_with_bad_plugins.yml -i inventory -v "$@"
|
||||||
|
unset ANSIBLE_CONNECTION_PLUGINS
|
||||||
|
|
||||||
|
ansible-playbook test_cannot_connect.yml -i inventory -v "$@"
|
||||||
|
|
||||||
|
if ansible-playbook test_base_cannot_connect.yml -i inventory -v "$@"; then
|
||||||
|
echo "Playbook intended to fail succeeded. Connection succeeded to nonexistent host"
|
||||||
|
exit 99
|
||||||
|
else
|
||||||
|
echo "Connection to nonexistent hosts failed without using ignore_unreachable. Success!"
|
||||||
|
fi
|
|
@ -0,0 +1,5 @@
|
||||||
|
- hosts: [localhost, nonexistent]
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
||||||
|
- hosts: [localhost, nonexistent]
|
||||||
|
ignore_unreachable: true
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
||||||
|
- hosts: nonexistent
|
||||||
|
ignore_unreachable: true
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
||||||
|
- name: This should print anyway
|
||||||
|
debug:
|
||||||
|
msg: This should print worked even though host was unreachable
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
||||||
|
register: should_fail
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'should_fail is unreachable'
|
||||||
|
- 'not (should_fail is skipped)'
|
||||||
|
- 'not (should_fail is failed)'
|
|
@ -0,0 +1,24 @@
|
||||||
|
- hosts: bad_put_file
|
||||||
|
gather_facts: false
|
||||||
|
ignore_unreachable: true
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
||||||
|
- hosts: bad_put_file
|
||||||
|
gather_facts: true
|
||||||
|
ignore_unreachable: true
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
||||||
|
- hosts: bad_exec
|
||||||
|
gather_facts: false
|
||||||
|
ignore_unreachable: true
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
||||||
|
- hosts: bad_exec
|
||||||
|
gather_facts: true
|
||||||
|
ignore_unreachable: true
|
||||||
|
tasks:
|
||||||
|
- name: Hi
|
||||||
|
ping:
|
|
@ -269,6 +269,7 @@ class TestStrategyBase(unittest.TestCase):
|
||||||
mock_task._role = None
|
mock_task._role = None
|
||||||
mock_task._parent = None
|
mock_task._parent = None
|
||||||
mock_task.ignore_errors = False
|
mock_task.ignore_errors = False
|
||||||
|
mock_task.ignore_unreachable = False
|
||||||
mock_task._uuid = uuid.uuid4()
|
mock_task._uuid = uuid.uuid4()
|
||||||
mock_task.loop = None
|
mock_task.loop = None
|
||||||
mock_task.copy.return_value = mock_task
|
mock_task.copy.return_value = mock_task
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue