Vladislav Rastrusny
Vladislav Rastrusny

Reputation: 30013

How to break `with_lines` cycle in Ansible?

I would like to use the following handler with Ansible:

- name: force ntp update
  shell: ntpdate {{item}}
  with_lines: /etc/ntpd.serverlist

But I want it to end execution after the first successful execution (the list contains ntpd servers with which you can attempt to sync. One is enough). How would I do that?

Upvotes: 4

Views: 7703

Answers (2)

Ivan Ogai
Ivan Ogai

Reputation: 1486

At least on Ansible 2.2.1.0 a when statement can break the loop, and the following works skipping all evaluations after the first success:

---
# test.yml

# run this playbook with: ansible-playbook -i localhost, test.yml

- hosts: localhost
connection: local
gather_facts: no
tasks:
  - name: check
    shell: "[ {{item}} -ne 2 ]"
    register: checks
    ignore_errors: yes
    changed_when: false
    when: checks is not defined or checks.rc != 0
    with_items: [2,3,2,4]
  - name: set
    set_fact: first_working={{ item.item }}
    when: "'rc' in item and item.rc == 0"
    with_items: "{{ checks.results }}"
  - debug: var=first_working
  # Note that first_working will be undefined if no check succeeded

This is the output:

PLAY [localhost] ***************************************************************

TASK [check] *******************************************************************
failed: [localhost] (item=2) => {"changed": false, "cmd": "[ 2 -ne 2 ]", "delta": "0:00:00.001735", "end": "2017-03-13 16:14:00.515372", "failed": true, "item": 2, "rc": 1, "start": "2017-03-13 16:14:00.513637", "stderr": "", "stdout": "", "stdout_lines": [], "warnings": []}
ok: [localhost] => (item=3)
skipping: [localhost] => (item=2)
skipping: [localhost] => (item=4)
...ignoring

TASK [set] *********************************************************************
skipping: [localhost] => (item={'_ansible_parsed': True, u'cmd': u'[ 2 -ne 2 ]', u'end': u'2017-03-13 16:14:00.515372', '_ansible_no_log': False, u'stdout': u'', '_ansible_item_result': True, u'changed': False, 'item': 2, u'delta': u'0:00:00.001735', u'stderr': u'', u'rc': 1, 'invocation': {'module_name': u'command', u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'_raw_params': u'[ 2 -ne 2 ]', u'removes': None, u'warn': True, u'chdir': None}}, 'stdout_lines': [], u'start': u'2017-03-13 16:14:00.513637', u'warnings': [], 'failed': True})
ok: [localhost] => (item={'_ansible_parsed': True, u'changed': False, u'stdout': u'', '_ansible_no_log': False, u'warnings': [], '_ansible_item_result': True, u'rc': 0, u'end': u'2017-03-13 16:14:00.615658', u'start': u'2017-03-13 16:14:00.613978', u'cmd': u'[ 3 -ne 2 ]', 'item': 3, u'delta': u'0:00:00.001680', 'invocation': {'module_name': u'command', u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'_raw_params': u'[ 3 -ne 2 ]', u'removes': None, u'warn': True, u'chdir': None}}, 'stdout_lines': [], u'stderr': u''})
skipping: [localhost] => (item={'skipped': True, '_ansible_no_log': False, 'skip_reason': u'Conditional check failed', '_ansible_item_result': True, 'item': 2, 'changed': False})
skipping: [localhost] => (item={'skipped': True, '_ansible_no_log': False, 'skip_reason': u'Conditional check failed', '_ansible_item_result': True, 'item': 4, 'changed': False})

TASK [debug] *******************************************************************
ok: [localhost] => {
    "first_working": "3"
}

Upvotes: 4

Bruce P
Bruce P

Reputation: 20759

That's a very interesting situation you have. I haven't tried this personally, but I wonder if something like this would work:

- name: force ntp update
  shell: ntpdate {{item}}
  with_lines: /etc/ntpd.serverlist
  register: ntp_result
  when: ntp_result is not defined or ntp_result.rc != 0
  ignore_errors: yes

So in a nutshell, each call to ntpdate should populate the ntp_result variable with the return code of the call to ntpdate. The when clause then ensures the loop continues if the variable doesn't exist (as it wouldn't have been populated during the first iteration), or if the ntpdate call failed (rc != 0). Telling Ansible to ignore any errors ensures that it continues looping if any of the calls to ntpdate does return an error.

The only real downside to this is that it won't directly notify you if none of the calls to ntpdate succeed. However you can probably follow this task with something along the lines of:

- name: fail if ntpdate fails
  fail: msg="All calls to ntpdate failed"
  when: ntp_result.rc != 0

If the last call resulted in a non-zero result from ntpdate then it means none of them succeeded.

Upvotes: 3

Related Questions