Skip to content
Permalink
Browse files

teach ansible about loop_control/break_when

this introduces the break_when loop_control attribute, which permits a
task loop to stop prematurely if the specified condition is true. This
can save time and reduce the amount of extraneous output if the
execution of a large loop can short circuited.
  • Loading branch information...
larsks committed Apr 23, 2019
1 parent cef536f commit 59810750590ca9bf2a3783d6093b1f82605fe506
@@ -0,0 +1,4 @@
---
minor_changes:
- "loop_control: add the break_when keyword to allow tasks to break out
of a loop prematurely."
@@ -412,6 +412,18 @@ For role authors, writing roles that allow loops, instead of dictating the requi

"{{ lookup('vars', ansible_loop_var) }}"

Breaking from a loop prematurely
--------------------------------
.. versionadded:: 2.8

You can cause a loop to exit prematurely using the ``break_when`` attribute to ``loop_control``. This is a conditional expression in the style of the ``changed_when`` or ``failed_when`` task attributes::

- debug:
msg: "This is item {{ item }}"
loop: "{{ range(100)|list }}"
loop_control:
break_when: item == 10

.. _migrating_to_loop:

Migrating from with_X to loop
@@ -290,6 +290,7 @@ def _run_loop(self, items):
label = None
loop_pause = 0
extended = False
break_when = None
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=self._job_vars)

# FIXME: move this to the object itself to allow post_validate to take care of templating (loop_control.post_validate)
@@ -299,8 +300,9 @@ def _run_loop(self, items):
loop_pause = templar.template(self._task.loop_control.pause)
extended = templar.template(self._task.loop_control.extended)

# This may be 'None',so it is templated below after we ensure a value and an item is assigned
# These may be 'None',so they are templated below after we ensure a value and an item is assigned
label = self._task.loop_control.label
break_when = self._task.loop_control.break_when

# ensure we always have a label
if label is None:
@@ -406,7 +408,15 @@ def _run_loop(self, items):
block=False,
)
results.append(res)
del task_vars[loop_var]

try:
if break_when is not None and break_when:
cond = Conditional(loader=self._loader)
cond.when = break_when
if cond.evaluate_conditional(templar, task_vars):
break
finally:
del task_vars[loop_var]

self._task.no_log = no_log

@@ -30,10 +30,15 @@ class LoopControl(FieldAttributeBase):
_label = FieldAttribute(isa='str')
_pause = FieldAttribute(isa='float', default=0)
_extended = FieldAttribute(isa='bool')
_break_when = FieldAttribute(isa='list', default=list)

def __init__(self):
super(LoopControl, self).__init__()

def _validate_break_when(self, attr, name, value):
if not isinstance(value, list):
setattr(self, name, [value])

@staticmethod
def load(data, variable_manager=None, loader=None):
t = LoopControl()
@@ -0,0 +1,40 @@
---
- name: loop_control/break_when
hosts: localhost
gather_facts: false
tasks:
- name: a loop that runs until completion
set_fact:
last_item: "{{ item }}"
loop: "{{ range(10)|list }}"

- name: assert that loop exited as expected
assert:
that:
- last_item == 9

- name: a loop that breaks prematurely using the loop var
set_fact:
last_item: "{{ item }}"
loop: "{{ range(10)|list }}"
loop_control:
break_when: item == 5

- name: assert that loop exited as expected
assert:
that:
- last_item == 5

- name: a loop that breaks prematurely using a registered var
debug:
msg:
item: "{{ item }}"
register: result
loop: "{{ range(10)|list }}"
loop_control:
break_when: result.msg.item == 5

- name: assert that loop exited as expected
assert:
that:
- result.results[-1].item == 5
@@ -9,3 +9,4 @@ MATCH='foo_label
bar_label'
[ "$(ansible-playbook label.yml "$@" |grep 'item='|sed -e 's/^.*(item=looped_var \(.*\)).*$/\1/')" == "${MATCH}" ]

ansible-playbook break_when.yml -v "$@"
@@ -145,6 +145,7 @@ def _copy(exclude_parent=False, exclude_tasks=False):

mock_task = MagicMock()
mock_task.copy.side_effect = _copy
mock_task.loop_control = {}

mock_play_context = MagicMock()

0 comments on commit 5981075

Please sign in to comment.
You can’t perform that action at this time.