interpolation of loop variables

338 views
Skip to first unread message

rjwagn...@gmail.com

unread,
Mar 13, 2024, 2:27:17 PM3/13/24
to Ansible Project
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
---
- hosts: localhost
  gather_facts: no
  tasks:
  - name: without templating
    assert:
      that: item is defined
    loop:
      - varA
      - varB
  - name: with templating
    assert:
      that: '{{ item }} is defined'
    loop:
      - varA
      - varB

#> ansible-playbook b.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'

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"
}

Why is templating required to interpolate the loop variable but not other (non loop) variables?  Furthermore, is there a way to write this task without templating and get the expected behavior?

Thanks
Rob

Todd Lewis

unread,
Mar 13, 2024, 4:55:45 PM3/13/24
to ansible...@googlegroups.com, uto...@gmail.com
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


--
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 ansible-proje...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ansible-project/d23f849b-0342-47d8-b2f4-0f1634cdf866n%40googlegroups.com.

-- 
Todd

Rowe, Walter P. (Fed)

unread,
Mar 14, 2024, 7:48:46 AM3/14/24
to 'Rowe, Walter P. (Fed)' via Ansible Project
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/pyt...@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
--
Walter Rowe, Division Chief
Infrastructure Services Division
Mobile: 202.355.4123

Rob Wagner

unread,
Mar 14, 2024, 10:12:20 AM3/14/24
to ansible...@googlegroups.com
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

---
- hosts: localhost
  gather_facts: no
  tasks:
  - name: a defined variable
    assert:
      that: ansible_forks is defined
  - name: an undefined variable
    assert:
      that: varA is defined
#> ansible-playbook c.yml

[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'

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

TASK [a defined variable] *************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [an undefined variable] **********************************************************
fatal: [localhost]: FAILED! => {

    "assertion": "varA is defined",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}

So, it seems that assert interpolates (non-templated) variables unless that variable is "item"

Rob

You received this message because you are subscribed to a topic in the Google Groups "Ansible Project" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ansible-project/fqqYt8AfU_o/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ansible-proje...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ansible-project/A767305C-C137-4E0B-B1FD-DAAC2DDD32A6%40nist.gov.

Matt Martz

unread,
Mar 14, 2024, 10:27:47 AM3/14/24
to ansible...@googlegroups.com
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


--
Matt Martz
@sivel
sivel.net

Rowe, Walter P. (Fed)

unread,
Mar 14, 2024, 10:30:32 AM3/14/24
to 'Rowe, Walter P. (Fed)' via Ansible Project
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
--
Walter Rowe, Division Chief
Infrastructure Services Division
Mobile: 202.355.4123

Rob Wagner

unread,
Mar 14, 2024, 10:44:26 AM3/14/24
to ansible...@googlegroups.com
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

Brian Coca

unread,
Mar 15, 2024, 3:02:54 PM3/15/24
to ansible...@googlegroups.com
You can also use the vars lookup: lookup('vars', item), which has
broader scope over hostvars.


--
----------
Brian Coca (he/him/yo)

Brian Coca

unread,
Mar 15, 2024, 3:03:43 PM3/15/24
to ansible...@googlegroups.com
also varnames lookup

Rob Wagner

unread,
Mar 16, 2024, 9:05:53 AM3/16/24
to ansible...@googlegroups.com
Hey Brian - any concerns with

assert:
  that: item in vars

Short and sweet!

Rob

On Fri, Mar 15, 2024 at 3:03 PM Brian Coca <bc...@redhat.com> wrote:
also varnames lookup


--
You received this message because you are subscribed to a topic in the Google Groups "Ansible Project" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ansible-project/fqqYt8AfU_o/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ansible-proje...@googlegroups.com.

Brian Coca

unread,
Mar 18, 2024, 10:36:07 AM3/18/24
to ansible...@googlegroups.com
`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.

Rob Wagner

unread,
Mar 19, 2024, 7:05:47 AM3/19/24
to ansible...@googlegroups.com
Got it, I’ll use the lookup. Thanks Brian

> On Mar 18, 2024, at 10:36 AM, Brian Coca <bc...@redhat.com> wrote:
>
> `vars` was undocumented and for internal use, we stopped using it
> --
> You received this message because you are subscribed to a topic in the Google Groups "Ansible Project" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/topic/ansible-project/fqqYt8AfU_o/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to ansible-proje...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/ansible-project/CACVha7di%2BDCjap0_OxetWpLsc%2Bj9x0AiMhDGRxW_NvY7zSD%3DxQ%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages