Migrating from 1.1 to 1.2, problems replacing "only_if" with "when"

Hi all,

I am starting to migrate my playbooks from Ansible 1.1 to Ansible 1.2
and I am getting a few problems.
To distinguish between CentOS and Ubuntu distributions, I used to set
two variables, "is_centos" and "is_ubuntu" in the following way:

    is_ubuntu: "'${ansible_distribution}' == 'Ubuntu'"
    is_centos: "'${ansible_distribution}' == 'CentOS'"

and then use the "only_if" statement in my tasks, like:

      - action: debug msg="This is a CentOS"
      only_if: $is_centos

    - action: debug msg="This is an Ubuntu machine"
      only_if: $is_ubuntu

and this worked as expected. Now I would like to use "when" instead of
"only_if", which is deprecated, but when I try the following:

    - action: debug msg="This is a CentOS"
      when: is_centos

    - action: debug msg="This is an Ubuntu machine"
      when: is_ubuntu

it always execute both tasks.
I've also tried to convert the "vars" section to a jinja2-like declaration:

    is_ubuntu: "{{ ansible_distribution == 'Ubuntu' }}"
    is_centos: "{{ ansible_distribution == 'CentOS' }}"

but still it does not work as expected.

This is the playbook I'm testing (on a Ubuntu machine):

I think the problem is related to the fact that the value of
"is_ubuntu" becomes a *string* instead of a boolean. If I use as a
conditional the following statement:

    when: is_ubuntu == 'True'

it works. Also, if I set the variable explicitly as a YAML boolean:

    vars:
      is_ubuntu: true
      is_centos: false

it works, but I don't know how to set these variables with YAML
booleans instead of strings using an expression...

Also this does not work:

    - action: set_fact is_ubuntu={{ ansible_distribution == 'Ubuntu' }}
    - action: set_fact is_centos={{ ansible_distribution == 'CentOS' }}

because "is_ubuntu" and "is_centos" still are evaluated as strings.

.a.

I don't know if this is related or not, but I've also found that "{{
variable }}" is treated differently from "${variable}".

In one of my playbooks I have something like:

    - include: common/tasks/iptables.yml trusted_hosts=${groups.slurm_clients}

the iptables.yml file contains:

- action: template dest={{ destfile }}
src=common/templates/etc/iptables.d/rules.v4.j2 owner=root group=root
mode=0644
  tags: iptables

and the template contains:

{% for host in trusted_hosts|sort %}
-A INPUT -s {{ host }} -j ACCEPT
{% endfor %}

the resulting output is:

-A INPUT -s compute001 -j ACCEPT
-A INPUT -s compute002 -j ACCEPT

but if I use {{groups.all}} instead of ${groups.all} the resulting output is:

-A INPUT -s [ -j ACCEPT
-A INPUT -s ' -j ACCEPT
-A INPUT -s c -j ACCEPT
-A INPUT -s o -j ACCEPT
-A INPUT -s m -j ACCEPT
-A INPUT -s p -j ACCEPT
-A INPUT -s u -j ACCEPT
-A INPUT -s t -j ACCEPT
-A INPUT -s e -j ACCEPT
-A INPUT -s 0 -j ACCEPT
-A INPUT -s 0 -j ACCEPT
-A INPUT -s 2 -j ACCEPT
-A INPUT -s ' -j ACCEPT
-A INPUT -s , -j ACCEPT
-A INPUT -s -j ACCEPT
-A INPUT -s ' -j ACCEPT
-A INPUT -s c -j ACCEPT
-A INPUT -s o -j ACCEPT
-A INPUT -s m -j ACCEPT
-A INPUT -s p -j ACCEPT
-A INPUT -s u -j ACCEPT
-A INPUT -s t -j ACCEPT
-A INPUT -s e -j ACCEPT
-A INPUT -s 0 -j ACCEPT
-A INPUT -s 0 -j ACCEPT
-A INPUT -s 1 -j ACCEPT
-A INPUT -s ' -j ACCEPT
-A INPUT -s ] -j ACCEPT

exactly like the "trusted_hosts" variable is treated as the string:
"['compute001', 'compute002']" instead of a list of strings.

.a.

There’s already a ticket on this, I’ll have to find it.

I strongly recommend looking into the “group_by” module for mapping configurations based on OS’es though, as the output is much nicer to not see all the ‘skipped’ steps.

groups.all is a list, you should walk over it in a loop.

There’s already a ticket on this, I’ll have to find it.

Thank you!

I strongly recommend looking into the “group_by” module for mapping configurations based on OS’es though, as the output is much nicer to not see all the ‘skipped’ steps.

I see your point, but I don’t really care about the output, and I want to migrate the syntax without changing too much. If I would have to use “group_by” for this kind of things I will have to re-structure all my playbooks :frowning:

groups.all is a list, you should walk over it in a loop.

I am not sure I understand your answer. I am looping, but inside a template. I want to pass a list of hosts to a template, so that it will produce a line for each item of the list, this is something I can’t do using “with_items”.
The problem is that when I use {{ groups.all }}, in the template I get a string instead of a list, so iterating over it means iterating over all the characters of the string representation of the list…

.a.

{% for x in groups[‘all’] %}
{{ x }}
{% endfor %}

{% for x in groups['all'] %}
   {{ x }}
{% endfor %}

I don't want it hardcoded in the template!

As I wrote before, this is what I want to have:

    - include: common/tasks/iptables.yml trusted_hosts=${groups.slurm_clients}

where "trusted_hosts" might change (in this example is
"slurm_clients", but could be just a list of hosts).
the iptables.yml file contains:

- action: template dest={{ destfile }}
src=common/templates/etc/iptables.d/rules.v4.j2 owner=root group=root
mode=0644
  tags: iptables

and the template contains:

{% for host in trusted_hosts|sort %}
-A INPUT -s {{ host }} -j ACCEPT
{% endfor %}

If I write "trusted_hosts={{groups.slurm_clients}}" the template will
receive a *string*, while if I write
"trusted_hosts=${groups.slurm_clients}" it receives a list, as
expected. Is this a bug or is the expected behavior? Is there a
difference between "${variable}" and "{{variable}}" or there should
not?

.a.

So we were intending to deprecate ${foo} but effectively it still works nicely for pass by reference, so I suspect it will be repurposed for such. In your case, it’s a matter of passing it in a dictionary rather than a string, since it can’t tell what you want the way it’s dereferenced above.

However this works today:

  • include: common/tasks/main/iptables.yml
    vars:
    trusted_hosts: ${groups.all}

Though I really want to unify this syntax so it plays more nicely with roles, also, i.e.

  • include: …
    trusted_hosts: ${groups.all}

Aka shorthand

  • { include: filename.yml, trusted_hosts: ${groups.all} }

The two latter forms are not implemented, but the first works for me today.

Hope this helps!

Thank you, this clarifies it. I think that for now I will continue to
use '${}' then, since it's not going to be deprecated.

.a.

It may be at some point, but in the case of passing a variable reference, it’s currently useful.