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.

Reply via email to