interpolation of loop variables

Hi all - I recently upgraded to Ansible core 2.15, and started hitting “Conditional is marked as unsafe, and cannot be evaluated.” As discussed at https://docs.ansible.com/ansible/latest/porting_guides/porting_guide_9.html, I removed templating from when and assert. All seemed fine, but then I discovered loop variables don’t appear to be interpolated in assert without templating.

As an example, consider the following playbook that aims to confirm two variables, varA and varB, are defined (in the sample output, they are not defined):
#> cat b.yml

Maybe it will facilitate understanding what’s going on if we name things what they really are:

---
- hosts: localhost
  gather_facts: no
  tasks:
  - name: Strings in a loop are indeed defined
    ignore_errors: true
    assert:
      that: item is defined
    loop:
      - RandomStringA
      - RandomStringB
      - ansible_forks

  - name: Strings in a loop are unlikely to be variable names
    ignore_errors: true
    assert:
      that: '{{ item }} is defined'
    loop:
      - UndefinedVariableNameA
      - UndefinedVariableNameB
      - ansible_forks

I sense that “with templating” does as expected?

% ansible --version

ansible [core 2.16.4]

config file = None

configured module search path = [‘/Users/wrowe/.ansible/plugins/modules’, ‘/usr/share/ansible/plugins/modules’]

ansible python module location = /opt/homebrew/lib/python3.11/site-packages/ansible

ansible collection location = /Users/wrowe/.ansible/collections:/usr/share/ansible/collections

executable location = /opt/homebrew/bin/ansible

python version = 3.11.8 (main, Feb 6 2024, 21:21:21) [Clang 15.0.0 (clang-1500.1.0.2.5)] (/opt/homebrew/opt/python@3.11/bin/python3.11)

jinja version = 3.1.3

libyaml = True

% ansible-playbook foo.yaml -i localhost,

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

TASK [without templating] **********************************************************************************************

ok: [localhost] => (item=varA) => {

“ansible_loop_var”: “item”,

“changed”: false,

“item”: “varA”,

“msg”: “All assertions passed”

}

ok: [localhost] => (item=varB) => {

“ansible_loop_var”: “item”,

“changed”: false,

“item”: “varB”,

“msg”: “All assertions passed”

}

TASK [with templating] *************************************************************************************************

failed: [localhost] (item=varA) => {

“ansible_loop_var”: “item”,

“assertion”: “varA is defined”,

“changed”: false,

“evaluated_to”: false,

“item”: “varA”,

“msg”: “Assertion failed”

}

failed: [localhost] (item=varB) => {

“ansible_loop_var”: “item”,

“assertion”: “varB is defined”,

“changed”: false,

“evaluated_to”: false,

“item”: “varB”,

“msg”: “Assertion failed”

}

PLAY RECAP *************************************************************************************************************

localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0

Walter

Hey Todd - apologies, I don’t understand your comment.

Hey Walter - yes, you are correct.

To elaborate on the use case. FIrst task in my playbooks confirms the user provided all the expected/required variables. I iterate over the list of variables and make sure they are defined with assert. The list of required variables is provided as a list to “loop.” Trouble I’m seeing is the items in the loop (i.e., the variable names) are not being interpolated by assert unless they are templated.

But if we ignore the loop for a second, and just pass a variable to assert, it does get interpolated without templating:

#> cat c.yml

You were previously making use of double interpolation, where the first rendered item into the literal string varA and then dereferencing the literal string varA into its value. When you do - item is defined now, it is of course defined, and defined as the literal string value of the loop item, because you only provided a name of a variable and didn’t dereference it into its value.

Probably the easiest solution is to look into hostvars, using item as just the name of the var:

  • assert:
    that:
  • hostvars[inventory_hostname][item] is defined
    loop:
  • varA
  • varB

In the first loop assert is evaluating that ‘item’ itself is defined.

In the second loop assert is evaluating whether a variable who’s name is stored in ‘item’ is defined.

% cat foo.yml


  • hosts: localhost

gather_facts: no

become: no

tasks:

  • name: without templating

assert:

that: “{{ item }} is defined”

loop:

  • varA

  • varB

% ansible-playbook -i localhost, foo.yml -e varB=string

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

TASK [without templating] **********************************************************************************************

failed: [localhost] (item=varA) => {

“ansible_loop_var”: “item”,

“assertion”: “varA is defined”,

“changed”: false,

“evaluated_to”: false,

“item”: “varA”,

“msg”: “Assertion failed”

}

ok: [localhost] => (item=varB) => {

“ansible_loop_var”: “item”,

“changed”: false,

“item”: “varB”,

“msg”: “All assertions passed”

}

PLAY RECAP *************************************************************************************************************

localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0

NOTE that on the command line I explicitly define varB but not varA.

Walter

Thanks Matt. That makes sense. And furthermore, thanks for the pointer to use hostvars. It’s actually required, since as soon as I switch back to templating, I get “Conditional is marked as unsafe.”

Appreciate the help, everyone!

Rob

You can also use the vars lookup: lookup('vars', item), which has
broader scope over hostvars.

also varnames lookup

Hey Brian - any concerns with

assert:

that: item in vars

Short and sweet!

Rob

`vars` was undocumented and for internal use, we stopped using it
internally but have been unable to remove it (and save many resources)
because people keep using(abusing) it.

Soon we will have 'variable deprecation' which will allow us to start
removing things like these, `vars` has long been the first target for
removal.

Got it, I’ll use the lookup. Thanks Brian