# short-circuit eval in templated string works only sometimes?

**URL:** <https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173>\
**Category:** Ansible Project\
**Created:** [May 15, 2021, 11:55pm UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173 "2021-05-15T23:55:24Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![Scott\_Mcdermott](https://avatars.discourse-cdn.com/v4/letter/s/958977/32.png) [@Scott\_Mcdermott](https://forum.ansible.com/u/Scott_Mcdermott)\
**Post date:** [May 15, 2021, 11:55pm UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/1 "2021-05-15T23:55:24Z")

</div>

Hello, why does short circuit only works for the first two cases:

- hosts: localhost  
&nbsp;&nbsp;become: false  
&nbsp;&nbsp;vars:  
&nbsp;&nbsp;&nbsp;&nbsp;ivar: '{{hostvars[inventory\_hostname].dne}}'  
&nbsp;&nbsp;tasks:  
&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: "works: {{'foo' or dne}}"  
&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: "works: {{'bar' or hostvars[inventory\_hostname].dne}}"  
&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: "fails: {{'baz' or ivar}}

The third debug will generate an error:

&nbsp;&nbsp;&nbsp;&nbsp;The task includes an option with an undefined variable.  
&nbsp;&nbsp;&nbsp;&nbsp;The error was: {{hostvars[inventory\_hostname].dne}}:  
&nbsp;&nbsp;&nbsp;&nbsp;'ansible.vars.hostvars.HostVarsVars object' has no attribute 'dne'

Why is it even looking up in hostvars at all? The non-empty  
string should evaluate True, so why is it reading the ORed  
condition? The same thing happens with "if true else" and  
ternary(true, ...) filter. It seems that it's properly  
short-circuiting in the first two cases, because the variable  
"dne" does not exist, and there is no error. Even more  
confusing, the expansion of host vars is the exact same in cases  
2 and 3, it's only going through an indirect variable in case 3.

This seems to happen regardless of the source of variables ie  
extra vars, vars\_files, etc.

Is this a bug, or what am I missing?

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [May 16, 2021, 6:54am UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/2 "2021-05-16T06:54:29Z")

</div>

Why don't you start debugging the variable?

&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var: dne

See "Minimal working example"  
[https://en.wikipedia.org/wiki/Minimal\_working\_example](https://en.wikipedia.org/wiki/Minimal_working_example)

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [May 16, 2021, 7:22am UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/3 "2021-05-16T07:22:59Z")

</div>

> \> Hello, why does short circuit only works for the first two cases:  
> \>  
> \> - hosts: localhost  
> \> become: false  
> \> vars:  
> \> ivar: '{{hostvars[inventory\_hostname].dne}}'  
> \> tasks:  
> \> - debug:  
> \> msg: "works: {{'foo' or dne}}"  
> \> - debug:  
> \> msg: "works: {{'bar' or hostvars[inventory\_hostname].dne}}"  
> \> - debug:  
> \> msg: "fails: {{'baz' or ivar}}  
> \>  
> \> The third debug will generate an error:  
> \>  
> \> The task includes an option with an undefined variable.  
> \> The error was: {{hostvars[inventory\_hostname].dne}}:  
> \> 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'dne'  
> \>  
> \> Why is it even looking up in hostvars at all? The non-empty  
> \> string should evaluate True, so why is it reading the ORed  
> \> condition? The same thing happens with "if true else" and  
> \> ternary(true, ...) filter. It seems that it's properly  
> \> short-circuiting in the first two cases, because the variable  
> \> "dne" does not exist, and there is no error. Even more  
> \> confusing, the expansion of host vars is the exact same in cases  
> \> 2 and 3, it's only going through an indirect variable in case 3.  
> \>  
> \> This seems to happen regardless of the source of variables ie  
> \> extra vars, vars\_files, etc.  
> \>  
> \> Is this a bug, or what am I missing?
> 
> Why don't you start debugging the variable?
> 
> &nbsp;&nbsp;- debug:  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var: dne
> 
> See "Minimal working example"  
> [https://en.wikipedia.org/wiki/Minimal\_working\_example](https://en.wikipedia.org/wiki/Minimal_working_example)

If the variable \*dne\* is not defined the playbook

\> cat playbook.yml  
- hosts: localhost  
&nbsp;&nbsp;&nbsp;vars:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ivar: "{{ hostvars[inventory\_hostname].dne }}"  
&nbsp;&nbsp;&nbsp;tasks:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var: ivar  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: "{{ 'foo' or dne }}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: "{{ 'foo' or ivar }}"

gives

\> ansible-playbook playbook.yml  
...  
TASK [debug]

---

<div class="post-metadata">

**Author:** ![Scott\_Mcdermott](https://avatars.discourse-cdn.com/v4/letter/s/958977/32.png) [@Scott\_Mcdermott](https://forum.ansible.com/u/Scott_Mcdermott)\
**Post date:** [May 16, 2021, 8:35am UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/4 "2021-05-16T08:35:46Z")

</div>

> third debug task fails because the expansion of the string  
> "{{ ... }}" fails before the \*or\* expression could be evaluated.

Sure, but why is it expanded at all? You're right that we can  
make my already-minimal test when variable Does Not Exist  
(dne) even shorter without needing to use hostvars:

- hosts: localhost  
&nbsp;&nbsp;become: false  
&nbsp;&nbsp;vars:  
&nbsp;&nbsp;&nbsp;&nbsp;ivar: '{{dne}}'  
&nbsp;&nbsp;tasks:  
&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: "{{'foo' or ivar}}"

> You'll need a default value if you want to use this kind of  
> "lazy evaluation" constructs ...

I had thought ansible variables were lazily evaluated already.  
In docs/docsite/rst/reference\_appendices/glossary.rst it says:

&nbsp;&nbsp;&nbsp;&nbsp;Lazy Evaluation  
&nbsp;&nbsp;&nbsp;&nbsp;In general, Ansible evaluates any variables in playbook  
&nbsp;&nbsp;&nbsp;&nbsp;content at the last possible second, which means  
&nbsp;&nbsp;&nbsp;&nbsp;that if you define a data structure that data structure itself  
&nbsp;&nbsp;&nbsp;&nbsp;can define variable values within it, and everything "just  
&nbsp;&nbsp;&nbsp;&nbsp;works" as you would expect. This also means variable  
&nbsp;&nbsp;&nbsp;&nbsp;strings can include other variables inside of those strings.

This seems to imply that things which aren't ever referenced,  
will never be expanded. There is even an open issue with a  
feature idea to force evaluation to be non-lazy:  
[https://github.com/ansible/ansible/issues/10374](https://github.com/ansible/ansible/issues/10374)

I cannot use a default because this whole thing is relying  
on whether it's defined or not later to take different actions.  
the fuller example is:

- name: determine\_subnet  
&nbsp;&nbsp;when: \>  
&nbsp;&nbsp;&nbsp;&nbsp;project == project\_bake  
&nbsp;&nbsp;&nbsp;&nbsp;or hostvars[target].ipnet is defined  
&nbsp;&nbsp;set\_fact:  
&nbsp;&nbsp;&nbsp;&nbsp;subnet: '{{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(project == project\_bake) | ternary(  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subnet\_bake, determined\_subnet  
&nbsp;&nbsp;&nbsp;&nbsp;)}}'

the problem here is that determined\_subnet is defined like this:

&nbsp;&nbsp;&nbsp;&nbsp;determined\_subnet: '{{subnets[ip\_cidrnet]}}'

and the chain for that is:

&nbsp;&nbsp;&nbsp;&nbsp;ip\_host: '{{hostvars[ip\_referent].ipnet}}'  
&nbsp;&nbsp;&nbsp;&nbsp;ip\_referent: '{{host | d(target | d(inventory\_hostname))}}'  
&nbsp;&nbsp;&nbsp;&nbsp;ip\_network: "{{ip\_host | ipaddr('network')}}"  
&nbsp;&nbsp;&nbsp;&nbsp;ip\_cidrnet: '{{ip\_network}}/{{ip\_prefix}}'

When ipnet (an inventory variable) is not defined,  
determined\_subnet is still being expanded, even though  
project == project\_bake, and should therefore never need  
to evaluate the second arg to ternary(). For this case, ipnet  
is not set.

I've made it work by splitting it into two set\_facts with  
cascading 'when' clauses, but I don't understand why it's  
happening in the first place. The evaluation does not seem  
to be lazy, or it would never need to evaluate an unused arg  
right? And it's the same behavior using ternary, if/else, or  
a disjunction ("or").

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [May 16, 2021, 8:51pm UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/5 "2021-05-16T20:51:02Z")

</div>

What value of \*subnet\* do you want to set when '(project !=  
project\_bake) and (determined\_subnet is undefined)' ?

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [May 16, 2021, 9:26pm UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/6 "2021-05-16T21:26:09Z")

</div>

When the corresponding alternatives shall always be defined the  
defaults can't hurt, e.g.

- set\_fact:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subnet: '{{ (project == project\_bake)|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ternary(subnet\_bake|default(None),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;determined\_subnet|default(None) }}'

In this case test sanity first

- fail:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: Variable undefined  
&nbsp;&nbsp;&nbsp;when: ((project == project\_bake) and (subnet\_bake is undefined)) or  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;((project != project\_bake) and (determined\_subnet is  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;undefined))

---

<div class="post-metadata">

**Author:** ![Scott\_Mcdermott](https://avatars.discourse-cdn.com/v4/letter/s/958977/32.png) [@Scott\_Mcdermott](https://forum.ansible.com/u/Scott_Mcdermott)\
**Post date:** [May 17, 2021, 6:18am UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/7 "2021-05-17T06:18:32Z")

</div>

determined\_subnet is always defined, except for project\_bake.  
project\_bake nodes don't have ipnet set (or any inventory vars),  
so the subnet url (for cloud api calls) can't be determined. So  
it gets hardcoded for that project. Hence, the need for the  
condition.

---

<div class="post-metadata">

**Author:** ![Scott\_Mcdermott](https://avatars.discourse-cdn.com/v4/letter/s/958977/32.png) [@Scott\_Mcdermott](https://forum.ansible.com/u/Scott_Mcdermott)\
**Post date:** [May 17, 2021, 6:32am UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/8 "2021-05-17T06:32:50Z")

</div>

Unfortunately that doesn't work, I tried it before. Because it's  
so indirect, it needs a default at the level that gets referenced,  
not the outer level of the indirection. It gets:

localhost failed | msg: The task includes an option with an  
undefined variable. The error was: {{subnets[ip\_cidrnet]}}:  
{{ip\_network}}/{{ip\_prefix}}: {{ip\_host | ipaddr('network')}}:  
{{hostvars[ip\_referent].ipnet}}:  
'ansible.vars.hostvars.HostVarsVars object' has no attribute  
'ipnet'

If only it would not try to dereference the thing, because it's  
never actually consulted for a value, as it's the OR with a  
true condition. That's the part I still don't get, why it's  
happening at all.

Anyways, I have worked around it at this point by splitting  
into two separate conditions with "when". I just wanted  
to understand why. I still don't understand why, but that  
happens sometimes... thanks.

---

<div class="post-metadata">

**Author:** ![Martin\_Krizek](https://avatars.discourse-cdn.com/v4/letter/m/d26b3c/32.png) [@Martin\_Krizek](https://forum.ansible.com/u/Martin_Krizek)\
**Post date:** [May 18, 2021, 12:54pm UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/9 "2021-05-18T12:54:07Z")

</div>

This is effectively a side-effect of functionality in Ansible's  
templating engine that allows nesting variables, like `ivar:  
'{{hostvars[inventory\_hostname].dne}}'` in your example. The second  
operand of `or` is actually resolved somewhere within Jinja templating  
machinery in all three cases. The difference is with the third one  
where Ansible's extended templating functionality gets `ivar` and  
tries to template it \*because it is a template\* (=this is what allows  
nesting variables) which causes the failure.

There are issues reported asking to change this behavior, see  
[https://github.com/ansible/ansible/issues/58835](https://github.com/ansible/ansible/issues/58835) and  
[https://github.com/ansible/ansible/issues/56017](https://github.com/ansible/ansible/issues/56017) (or an interesting  
example that was filed recently in  
[https://github.com/ansible/ansible/issues/74594](https://github.com/ansible/ansible/issues/74594)).

The problem is that while changing this might result in expected  
behavior in this case, it will break other scenarios. However the  
above issues are open so feel free to give your input there.

Hopefully this clarifies the matter a bit.

Thanks,  
Martin

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [May 18, 2021, 2:20pm UTC](https://forum.ansible.com/t/short-circuit-eval-in-templated-string-works-only-sometimes/35173/10 "2021-05-18T14:20:57Z")

</div>

> This is effectively a side-effect of functionality in Ansible's  
> templating engine that allows nesting variables ...

FWIW, one more of this kind not mentioned yet, I think

&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var: my\_dict.a  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vars:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my\_dict:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b: "{{ undefined\_var }}"

gives

&nbsp;&nbsp;&nbsp;&nbsp;my\_dict.a: VARIABLE IS NOT DEFINED!
