diff --git a/changelogs/fragments/end_batch-meta-task.yml b/changelogs/fragments/end_batch-meta-task.yml new file mode 100644 index 00000000000..9817307d979 --- /dev/null +++ b/changelogs/fragments/end_batch-meta-task.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add ``end_batch`` meta task. diff --git a/lib/ansible/modules/meta.py b/lib/ansible/modules/meta.py index 6381636b3cc..dc82e3c5cdd 100644 --- a/lib/ansible/modules/meta.py +++ b/lib/ansible/modules/meta.py @@ -33,7 +33,9 @@ options: - C(end_play) (added in Ansible 2.2) causes the play to end without failing the host(s). Note that this affects all hosts. - C(reset_connection) (added in Ansible 2.3) interrupts a persistent connection (i.e. ssh + control persist) - C(end_host) (added in Ansible 2.8) is a per-host variation of C(end_play). Causes the play to end for the current host without failing it. - choices: [ clear_facts, clear_host_errors, end_host, end_play, flush_handlers, noop, refresh_inventory, reset_connection ] + - C(end_batch) (added in Ansible 2.12) causes the current batch (see C(serial)) to end without failing the host(s). + Note that with C(serial=0) or undefined this behaves the same as C(end_play). + choices: [ clear_facts, clear_host_errors, end_host, end_play, flush_handlers, noop, refresh_inventory, reset_connection, end_batch ] required: true notes: - C(meta) is not really a module nor action_plugin as such it cannot be overwritten. diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 7f4dafa4c9b..b92fcb15b21 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -1156,6 +1156,15 @@ class StrategyBase: else: skipped = True skip_reason += ', not clearing host error state for %s' % target_host.name + elif meta_action == 'end_batch': + if _evaluate_conditional(target_host): + for host in self._inventory.get_hosts(iterator._play.hosts): + if host.name not in self._tqm._unreachable_hosts: + iterator._host_states[host.name].run_state = iterator.ITERATING_COMPLETE + msg = "ending batch" + else: + skipped = True + skip_reason += ', continuing current batch' elif meta_action == 'end_play': if _evaluate_conditional(target_host): for host in self._inventory.get_hosts(iterator._play.hosts): diff --git a/test/integration/targets/meta_tasks/runme.sh b/test/integration/targets/meta_tasks/runme.sh index cc951bbba56..f5916ec7627 100755 --- a/test/integration/targets/meta_tasks/runme.sh +++ b/test/integration/targets/meta_tasks/runme.sh @@ -56,3 +56,12 @@ for test_strategy in linear free; do grep -q "META: ending play" <<< "$out" grep -qv 'Failed to end using end_play' <<< "$out" done + +# test end_batch meta task +for test_strategy in linear free; do + out="$(ansible-playbook test_end_batch.yml -i inventory.yml -e test_strategy=$test_strategy -vv "$@")" + + [ "$(grep -c "Using end_batch" <<< "$out" )" -eq 2 ] + [ "$(grep -c "META: ending batch" <<< "$out" )" -eq 2 ] + grep -qv 'Failed to end_batch' <<< "$out" +done diff --git a/test/integration/targets/meta_tasks/test_end_batch.yml b/test/integration/targets/meta_tasks/test_end_batch.yml new file mode 100644 index 00000000000..4af020afa34 --- /dev/null +++ b/test/integration/targets/meta_tasks/test_end_batch.yml @@ -0,0 +1,13 @@ +- name: Testing end_batch with strategy {{ test_strategy | default('linear') }} + hosts: testhost:testhost2 + gather_facts: no + serial: 1 + strategy: "{{ test_strategy | default('linear') }}" + tasks: + - debug: + msg: "Using end_batch, current host: {{ inventory_hostname }}, current batch: {{ ansible_play_batch }}" + + - meta: end_batch + + - fail: + msg: "Failed to end_batch, current host: {{ inventory_hostname }}, current batch: {{ ansible_play_batch }}"