My use case involved roles. I had something like
- hosts: web:app:db
roles:
- role: myrole
when: color == "blue"
In the role, there was a task that ran on localhost (via delegate_to), but
only once (via run_once) for the whole batch of hosts.
Everything worked fine, except that if the first host in inventory happened
not to be blue, the run_once caused the localhost task to be skipped. The
order of the hosts in inventory was completely arbitrary -- these were EC2
instances at AWS.
The eventual workaround was to add the `when` to every single task in the
role *except* the run_once one, which made both the playbook and the role
less readable.
I don't have any hope that the Ansible team will ever address this; for
whatever reason, this use case is relatively common among people who aren't
on the Ansible team, and impossible to explain to the Ansible team in a way
that anyone finds convincing. We haven't yet found a blocker that we
couldn't work around in one ugly-ass way or another.
On Monday, March 19, 2018 at 6:06:13 PM UTC-4, Alex Hunt wrote:
>
> Heres some actual execution output for my second example, and for the one
> from @Matt Martz.
>
> Mine
> (.env) [exabeam@ip-10-10-2-162 test]$ ll
> total 16
> -rw-rw-r-- 1 exabeam exabeam 79 Mar 19 21:57 inner-task.yml
> -rw-rw-r-- 1 exabeam exabeam 206 Mar 19 21:59 inventory
> -rw-rw-r-- 1 exabeam exabeam 83 Mar 19 21:57 outer-task.yml
> -rw-rw-r-- 1 exabeam exabeam 70 Mar 19 21:56 play.yml
> (.env) [exabeam@ip-10-10-2-162 test]$ cat inventory
> [all]
> host1 ansible_host=10.10.2.162
> host2 ansible_host=10.10.2.173
> host3 ansible_host=10.10.2.206
>
> [all:vars]
> ansible_port=22
> ansible_ssh_private_key_file=/home/exabeam/devkey.pem
> ansible_ssh_user=exabeam
> (.env) [exabeam@ip-10-10-2-162 test]$ cat play.yml
> - name: Test Play
> hosts: all
> tasks:
> - include: outer-task.yml
> (.env) [exabeam@ip-10-10-2-162 test]$ cat outer-task.yml
> - name: Outer task
> include: inner-task.yml
> when: inventory_hostname != 'host1'
> (.env) [exabeam@ip-10-10-2-162 test]$ cat inner-task.yml
> - name: Inner task
> command: hostname
> run_once: True
> delegate_to: 'host2'
> (.env) [exabeam@ip-10-10-2-162 test]$ ansible-playbook -i inventory play.
> yml
>
> PLAY [Test Play]
> ***************************************************************
>
> TASK [setup]
> *******************************************************************
> ok: [host1]
> ok: [host3]
> ok: [host2]
>
> TASK [Inner task]
> **************************************************************
> skipping: [host1]
>
> PLAY RECAP
> *********************************************************************
> host1 : ok=1 changed=0 unreachable=0 failed=
> 0
> host2 : ok=1 changed=0 unreachable=0 failed=
> 0
> host3 : ok=1 changed=0 unreachable=0 failed=
> 0
>
> @Matt Martz' (the only difference is the delegate_to line):
> (.env) [exabeam@ip-10-10-2-162 test]$ cat inner-task.yml
> - name: Inner task
> command: hostname
> run_once: True
> delegate_to: "{{ ansible_play_hosts|first }}"
> (.env) [exabeam@ip-10-10-2-162 test]$ ansible-playbook -i inventory play.
> yml
>
> PLAY [Test Play]
> ***************************************************************
>
> TASK [setup]
> *******************************************************************
> ok: [host1]
> ok: [host3]
> ok: [host2]
>
> TASK [Inner task]
> **************************************************************
> skipping: [host1]
>
> PLAY RECAP
> *********************************************************************
> host1 : ok=1 changed=0 unreachable=0 failed=
> 0
> host2 : ok=1 changed=0 unreachable=0 failed=
> 0
> host3 : ok=1 changed=0 unreachable=0 failed=
> 0
>
> In both cases, the task in inner-task.yml is skipped, since host1 does not
> match the when clause in outer-task.yml. The delegate_to makes no
> difference. If I left that out, it would behave the same. The issue is not
> where it runs, but that it doesn't run at all. It would be perfectly
> acceptable for it to execute on host1, which is in line with the docs, but
> it doesn't run at all.
>
> On Monday, March 19, 2018 at 2:53:44 PM UTC-7, Alex Hunt wrote:
>>
>> @Matt Martz I have no problem with how delegate_to works. I don't care if
>> it executes on host1. The stuff you wrote does not actually change *if
>> it gets run at all*, only where it would be run if host1 had failed a
>> prior task. It isn't that it tries to run it and fails, but that it doesn't
>> even try to run. The host1 is still in the list of play_hosts, even if it
>> is skipped, so it is still used to determine if we should run. *I have
>> actually run the code you wrote, and it does not solve this issue.*
>>
>> If you're worried about breaking some obscure code that relies on
>> skipping the task entirely based on the order of the hosts in the
>> inventory, that's fine, but the community needs a way to reliably decide to
>> run exactly one time. It's totally fine for it to be a new directive
>> "actually_run_once".
>>
>> Where it executes doesn't matter, but that it executes at all, does. It
>> is trivial to use the properly working "delegate_to" clause to control
>> where the task is actually run, but it has no effect on if the ansible
>> tries to run it in the first place.
>>
>> My interpretation of your code is that you are trying to supply a host to
>> execute the task on in the case that host1 has failed out of the execution
>> due to a prior task failure. In my example, host1 is reachable, working
>> properly, and has not failed any tasks. It is simply skipped due to a when
>> clause that is not attached to the task with run_once. It would be
>> perfectly acceptable and in line with the documentation for the task to
>> execute on host1, but instead the entire task is skipped.
>>
>> The problem is that the decision to run the task is tied to the first
>> host in the play, not that the execution defaults to the first host in the
>> play.
>>
>> On Monday, March 19, 2018 at 2:27:46 PM UTC-7, Matt Martz wrote:
>>>
>>> I fully understand what you are saying. However the difference here is
>>> that you have a misunderstanding about the feature. You have an idea in
>>> your head, that doesn't match the implementation.
>>>
>>> The way `run_once` works, is that it defaults to execute on the first
>>> host in the list of hosts on the play, as defined by inventory. If that
>>> host is failed, that task is then skipped. Using `delegate_to` offers you
>>> a way to avoid your specific scenario, as it permits you to change what
>>> host ansible targets. Take special care to re-read what I wrote, instead
>>> of ignoring it. I recommend using `ansible_play_hosts` in `delegate_to` to
>>> ensure it always targets an available host. But that may not meet every
>>> persons requirements. You will have to implement a `delegate_to` on that
>>> host that properly reflects what host to operate on if the "first" host is
>>> not available.
>>>
>>> Unfortunately, your expectation doesn't align with the implementation
>>> and our definition of what is expected here.
>>>
>>> I'm telling you how to do what you want, within the context of how
>>> `run_once` actually works. We have no intentions on changing how `run_once`
>>> works. You'll have to operate within the confines of how run_once
>>> *actually* operates.
>>>
>>> On Mon, Mar 19, 2018 at 4:18 PM, Alex Hunt <[email protected]> wrote:
>>>
>>>> The issue has nothing to do with delegate_to. The issue has to do with
>>>> whether it decides to run at all, which delegate_to has no effect on. I
>>>> don't care which host it runs on, and if I did, I could use delegate_to as
>>>> you have noted. The delegate_to directive works properly.
>>>>
>>>> It is totally fine for it to execute on the first host in the
>>>> inventory, as long as it runs when that host is skipped for the included
>>>> task book. I apologize if I'm not being clear about what the issue is.
>>>>
>>>> Here's the same example, updated with a delegate_to, since everyone
>>>> seems to think that matters.
>>>>
>>>> Inventory
>>>> [all]
>>>> host1
>>>> host2
>>>> host3
>>>>
>>>> Playbook
>>>> - name: Test Play
>>>> hosts: all
>>>> tasks:
>>>> - include: outer-task.yml
>>>>
>>>> outer-task.yml
>>>> - name: Outer task
>>>> include: inner-task.yml
>>>> when: inventory_hostname != 'host1'
>>>>
>>>> inner-task.yml
>>>> - name: Inner task
>>>> command: do_something
>>>> run_once: True
>>>> delegate_to: 'host2'
>>>>
>>>> In this example, it should run on host2, but it does not, since host1
>>>> skips the entire inner-task.yml. This is the problem.
>>>>
>>>> In my original example, I didn't care if it ran on host1, as long as it
>>>> ran, but it doesn't run at all.
>>>>
>>>> On Monday, March 19, 2018 at 1:54:06 PM UTC-7, Matt Martz wrote:
>>>>>
>>>>> The behavior is documented via that information provided
>>>>> above. `run_once` in it's current form is designed to be consistent and
>>>>> predictable in which host is picked to execute the task against.
>>>>>
>>>>> > When “run_once” is not used with “delegate_to” it will execute on
>>>>> the first host, as defined by inventory, in the group(s) of hosts
>>>>> targeted
>>>>> by the play
>>>>>
>>>>> If the first host is failed, it is removed from the play list, and
>>>>> run_once will therefore be skipped.
>>>>>
>>>>> Using `delegate_to` allows you to define what you believe is
>>>>> consistent or predictable. If you don't care what host it executes on,
>>>>> using `delegate_to` can be made to do what you want:
>>>>>
>>>>> - command: whoami
>>>>> run_once: true
>>>>> delegate_to: "{{ ansible_play_hosts|first }}"
>>>>>
>>>>> `ansible_play_hosts` is updated as hosts fail.
>>>>>
>>>>> So if it started as:
>>>>>
>>>>> "ansible_play_hosts": [
>>>>> "host0",
>>>>> "host1",
>>>>> "host2",
>>>>> "host3",
>>>>> "host4"
>>>>> ]
>>>>>
>>>>> and `host0` failed, that `delegate_to` above will utilize `host1`.
>>>>> Instead of first, something like `random` could be used too.
>>>>>
>>>>> If you wish to add further, constructive, clarification to the docs,
>>>>> and potentially examples such as the one I provide above, feel free to
>>>>> submit a documentation pull request.
>>>>>
>>>>> On Mon, Mar 19, 2018 at 3:33 PM, Alex Hunt <[email protected]> wrote:
>>>>>
>>>>>> I think you're confused by what the issue is. Whether I use
>>>>>> delegate_to or not is irrelevant. I don't care which host it runs on,
>>>>>> and
>>>>>> if I did, I would use the delegate_to. Even if I use delegate_to, it
>>>>>> will
>>>>>> still be skipped, since it evaluates whether to run the task at all
>>>>>> based
>>>>>> on the first host. I'm sorry I didn't include a delegate_to in my
>>>>>> example,
>>>>>> which lead to this confusion.
>>>>>>
>>>>>>
>>>>>> http://docs.ansible.com/ansible/latest/playbooks_delegation.html#run-once
>>>>>>
>>>>>> makes no mention of the fact that *even with delegate_to* it decides
>>>>>> *whether
>>>>>> to run at all* based on the first host. The mention of delegate_to
>>>>>> actually makes this more confusing, since that The delegate_to should
>>>>>> control where the execution happens (something irrelevant to this
>>>>>> issue),
>>>>>> not whether it runs at all. That part is at least consistent, since
>>>>>> delegate_to does not control whether to run it.
>>>>>>
>>>>>> The issue is that run_once is not actually running once. It is "run
>>>>>> only if the first node in the play says to run it", not "run one time if
>>>>>> it
>>>>>> should run for any host in the play". The latter is intuitive behavior.
>>>>>> You
>>>>>> talk of predictable results, and it is not predictable to have behavior
>>>>>> that changes based on the order of hosts in your inventory file (the
>>>>>> current behavior).
>>>>>>
>>>>>> Please note that in my example, the when clause is NOT on the task
>>>>>> with run_once. If we make reusable code, we may be including that piece
>>>>>> in
>>>>>> many places, with or without the when clause.
>>>>>>
>>>>>>
>>>>>> On Monday, March 19, 2018 at 12:15:13 PM UTC-7, flowerysong wrote:
>>>>>>>
>>>>>>> On Monday, March 19, 2018 at 2:49:12 PM UTC-4, Alex Hunt wrote:
>>>>>>>>
>>>>>>>> When running a task with run_once, if the first node is skipped,
>>>>>>>> the entire task is skipped, rather than running on the first host that
>>>>>>>> is
>>>>>>>> not skipped.
>>>>>>>>
>>>>>>>> This behavior is not what is intuitively understood, this behavior
>>>>>>>> is not mentioned in the docs, and this behavior is almost certainly
>>>>>>>> not
>>>>>>>> what most of people want it to do. There are discussions of this in
>>>>>>>> multiple github issues, the most detailed of which is at
>>>>>>>> https://github.com/ansible/ansible/issues/19966, but there are
>>>>>>>> also at least https://github.com/ansible/ansible/issues/11496,
>>>>>>>> https://github.com/ansible/ansible/issues/13226, and
>>>>>>>> https://github.com/ansible/ansible/issues/23594.
>>>>>>>>
>>>>>>>
>>>>>>> It may confuse some people, but it's both the documented behaviour
>>>>>>> and the least surprising way to do things. Conditionals should not
>>>>>>> affect
>>>>>>> the number of times a run_once task is evaluated even if they result in
>>>>>>> the
>>>>>>> task being skipped.
>>>>>>>
>>>>>>>
>>>>>>> https://docs.ansible.com/ansible/latest/playbooks_delegation.html#run-once
>>>>>>>
>>>>>>> says: "When “run_once” is not used with “delegate_to” it will execute
>>>>>>> on
>>>>>>> the first host, as defined by inventory, in the group(s) of hosts
>>>>>>> targeted
>>>>>>> by the play - e.g. webservers[0] if the play targeted “hosts:
>>>>>>> webservers”."
>>>>>>>
>>>>>>>
>>>>>>>> Below is an untested simple example of a scenario that would skip
>>>>>>>> the run_once task, when it should (according to the docs, and common
>>>>>>>> sense)
>>>>>>>> run on one of either host2 or host3.
>>>>>>>>
>>>>>>>> Inventory
>>>>>>>> [all]
>>>>>>>> host1
>>>>>>>> host2
>>>>>>>> host3
>>>>>>>>
>>>>>>>> Playbook
>>>>>>>> - name: Test Play
>>>>>>>> hosts: all
>>>>>>>> tasks:
>>>>>>>> - include: outer-task.yml
>>>>>>>>
>>>>>>>> outer-task.yml
>>>>>>>> - name: Outer task
>>>>>>>> include: inner-task.yml
>>>>>>>> when: inventory_hostname != 'host1'
>>>>>>>>
>>>>>>>> inner-task.yml
>>>>>>>> - name: Inner task
>>>>>>>> command: do_something
>>>>>>>> run_once: True
>>>>>>>>
>>>>>>>> This issue is exacerbated by the fact that the inner task may have
>>>>>>>> no idea why the first host is skipped (IE: we're including a reusable
>>>>>>>> task
>>>>>>>> that may get run many times in different ways). In those cases, there
>>>>>>>> is no
>>>>>>>> way to work around the issue with a simple `when: inventory_hostname
>>>>>>>> ==
>>>>>>>> something`, since we don't know what to check against.
>>>>>>>>
>>>>>>>
>>>>>>> You're mixing different ways of limiting where a task runs, with
>>>>>>> predictable results (the task is assigned to one host, and the
>>>>>>> conditional
>>>>>>> results in it being skipped). If you don't care which host it runs on,
>>>>>>> use
>>>>>>> run_once without a conditional. If you want to run it on a specific
>>>>>>> host,
>>>>>>> use delegate_to with run_once or a conditional without run_once.
>>>>>>>
>>>>>> --
>>>>>> You received this message because you are subscribed to the Google
>>>>>> Groups "Ansible Project" group.
>>>>>> To unsubscribe from this group and stop receiving emails from it,
>>>>>> send an email to [email protected].
>>>>>> To post to this group, send email to [email protected].
>>>>>> To view this discussion on the web visit
>>>>>> https://groups.google.com/d/msgid/ansible-project/1b542a8f-8f75-462a-8c11-0fd0c76054dc%40googlegroups.com
>>>>>>
>>>>>> <https://groups.google.com/d/msgid/ansible-project/1b542a8f-8f75-462a-8c11-0fd0c76054dc%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>>> .
>>>>>>
>>>>>> For more options, visit https://groups.google.com/d/optout.
>>>>>>
>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Matt Martz
>>>>> @sivel
>>>>> sivel.net
>>>>>
>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "Ansible Project" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to [email protected].
>>>> To post to this group, send email to [email protected].
>>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/ansible-project/38cf4ed5-6285-464a-a81a-68a44d04709a%40googlegroups.com
>>>>
>>>> <https://groups.google.com/d/msgid/ansible-project/38cf4ed5-6285-464a-a81a-68a44d04709a%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>>> For more options, visit https://groups.google.com/d/optout.
>>>>
>>>
>>>
>>>
>>> --
>>> Matt Martz
>>> @sivel
>>> sivel.net
>>>
>>
--
You received this message because you are subscribed to the Google Groups
"Ansible Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/ansible-project/38a76362-461d-47e5-8cd5-c1620f2f3410%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.