Run once with when statement causes only skipped action to run

Hey,

I’m seeing some weird behavior that does not seem intuitive when trying to use run_once with when. Intuitively I think this should run an action one time when the conditional is met, however what I’m seeing is that a skipped action is ran (because the conditional is false) and nothing actually changes. Is there anyway to work around this? Should I file a bug?

Thanks,
Dave

By default run_once only executes the task against the 1st host in the list of hosts in the play batch. As such, the when statement only uses the variables from that 1 host.

Effectively it is doing:

run_once: true
delegate_to: “{{ ansible_play_batch[0] }}”

So you will either need to ensure that your when statement applies for that first host, or potentially use delegate_to to specify another host.

There's a bunch of tickets about run_once and skipped hosts;
https://github.com/ansible/ansible/issues/19966 is my personal favorite.

                                      -Josh (jbs@care.com)

(apologies for the automatic corporate disclaimer that follows)

This email is intended for the person(s) to whom it is addressed and may contain information that is PRIVILEGED or CONFIDENTIAL. Any unauthorized use, distribution, copying, or disclosure by any person other than the addressee(s) is strictly prohibited. If you have received this email in error, please notify the sender immediately by return email and delete the message and any attachments from your system.

Thanks for the replies. That’s an interesting idea about using delegate_to, perhaps I can use a jinja2 filter to only delegate_to a host matching the criteria in the when.

I was considering creating an in-memory group of only the hosts that matched the when but that sounds a bit verbose and a pain every time I want to run something once on a specific set of hosts.

Dave

I can’t think of a clever way to use delegate_to, I really need the run_once functionality since these are all running in parallel.

Weirdly, when I try to create an in-memory host group using when and add_host the task only gets executed one time (on a skipped host). I don’t have run_once on this task so I’m not sure why all my nodes aren’t executing it. Any ideas?

Anyone have a good workaround for this issue?

Thanks

Weirdly, when I try to create an in-memory host group using when and
add_host the task only gets executed one time (on a skipped host). I
don't have run_once on this task so I'm not sure why all my nodes aren't
executing it. Any ideas?

add_host is implicitly run_once; there's a note about it at the bottom of
http://docs.ansible.com/ansible/latest/add_host_module.html saying "This
module bypasses the play host loop and only runs once for all the hosts in
the play, if you need it to iterate use a with_ directive." Something like

  - add_host:
      groups: cool_new_group
      name: "{{ item }}"
    changed_when: False
    with_items: "{{ play_hosts }}"

might do what you need, depending what you need. :^)

                                      -Josh (jbs@care.com)

(apologies for the automatic corporate disclaimer that follows)

This email is intended for the person(s) to whom it is addressed and may contain information that is PRIVILEGED or CONFIDENTIAL. Any unauthorized use, distribution, copying, or disclosure by any person other than the addressee(s) is strictly prohibited. If you have received this email in error, please notify the sender immediately by return email and delete the message and any attachments from your system.

Thanks for the help, Josh.

I actually am trying to run a command once on a certain set of EC2 instances I’m managing that match a specific tag. This seems like a very reasonable thing to do and yet the run_once skipping functionality makes this quite difficult. I managed to find two similar but different ways to do this (though not exactly elegant), which I’ll post for future reference. I was inspired by https://groups.google.com/forum/#!topic/ansible-project/cpnrBRxLy0E and https://imil.net/blog/2016/08/05/Ansible_and_AWS_ASG

Method 1 (serial 1 is important due to the add_host behavior Josh pointed out). Run the playbook with the AWS dynamic inventory script (-i ec2.py)

  • hosts: ec2
    user: ubuntu
    serial: 1
    gather_facts: false
    tasks:

  • name: add only nodes that don’t match your_tag_name to in-memory host group
    add_host:
    name: “{{ inventory_hostname }}”
    groups: your_hosts
    when: ec2_tag_Name != your_tag_name

and then run the rest of your playbook not serially and perform run_once actions like this:

  • hosts: ec2
    user: ubuntu
    gather_facts: true
    tasks:

  • name: Run once on your_tag_name
    shell: echo yes >> yes.txt
    run_once: true
    delegate_to: “{{ groups[‘your_hosts’][0] }}”

Method 2: use the ec2_instance_facts module to create the in-memory group. This requires a tag name variable or constant you can reference to match instances with

  • hosts: localhost
    user: ubuntu
    connection: local
    gather_facts: true
    tasks:

  • name: get ec2 remote facts

ec2_instance_facts:
region: “{{ aws_region }}”
register: ec2

  • name: create an in memory group of only nodes with your_tag_name
    add_host:
    name: “{{ item }}”
    groups: your_hosts
    with_items: “{{ ec2.instances | selectattr(‘state.name’, ‘equalto’, ‘running’) | selectattr(‘tags.Name’, ‘equalto’, your_tag_name ) | map(attribute=‘public_ip_address’)|list }}”

Then performing the run_once is the same.

Either way, if you want to use the information from ec2.py then you’ll end up having two hosts sections and some verbosity. I suppose with the ec2_instance_facts one could potentially create groups of EC2 hosts and run the playbook all with hosts: localhost and delegate tasks to certain groups? I haven’t tried this as I’ve spend enough time getting run_once to work for now.

Dave

If you’re using ec2.py to get your dynamic inventory you might use the dynamic groups created from tags (tag_NAME_VALUE). If you dynamically create the host before having to access it you can use:

- meta: refresh_inventory

to re-run the inventory script (which should discover newly created EC2 instances).

in that case you simply run the new play in the playbook against the hosts matching the tag:
hosts: tag_NAME_VALUE

kind regards
Pshem

Thanks for the suggestion. This would be nice except I am using variable tag names defined in a config file, furthermore the tag names can’t have underscores (a requirement of the AWS ELB module) only dashes so I don’t even think I could map things even if it was possible to do something like the below although I’m not even sure you can use variables in hosts definitions like this?

  • hosts: “‘tag_Name_’ + {{ my_tag_variable }}”

Even if this worked I’d be writing another whole hosts section every time I wanted to run once on a given tag. The ‘run once on a tag’ commands are interspersed throughout the playbook so this would get annoying. At least with the way above I have at most one extra hosts section.

Thanks,
Dave