remove bare var handling in conditionals (#51030)

* remove bare var handling in conditionals

  this makes top level and multilevel vars (dicts keys) behave the same
  it will require adding |bool for 'string comparissons' in indirect templates

  - added new tests to ensure uniform handling
  - switched to 'is' testing for status
  - changed warning to 'conditional' as 'when:' is not only place it gets triggered

* updated to include toggle and deprecation

* fix deprecated

* updated tests to handle toggle

* fixed typo and added note about the future
pull/51517/head
Brian Coca 6 years ago committed by ansibot
parent 0493ef359a
commit 4a0fceaa3b

@ -0,0 +1,3 @@
bugfixes:
- remove bare var handling from conditionals (not needed since we removed bare vars from `with_` loops) to normalize handling of
variable values, no matter if the string value comes from a top level variable or from a dictionary key or subkey

@ -318,6 +318,19 @@ COLOR_WARN:
env: [{name: ANSIBLE_COLOR_WARN}]
ini:
- {key: warn, section: colors}
CONDITINAL_BARE_VARS:
name: Allow bare variable evaluation in conditionals
default: True
type: boolean
description:
- With this setting on (True), runing conditional evaluation 'var' is treated differently 'var.subkey' as the first is evaluted
directly while the second goes though the Jinja2 parser. But 'false' strings in 'var' get evaluated as booleans.
- With this settting off they both evalutate the same but in cases in which 'var' was 'false' (a string) it won't get evaluated as a boolean anymore.
- Currently this setting defaults to 'True' but will soon change to 'False' and the setting itself will be removed in the future.
- Expect the default to change in version 2.10 and that this setting eventually will be deprecated after 2.12
env: [{name: ANSIBLE_CONDITIONAL_BARE_VARS}]
ini:
- {key: conditional_bare_variables, section: defaults}
ACTION_WARNINGS:
name: Toggle action warnings
default: True

@ -25,6 +25,7 @@ import re
from jinja2.compiler import generate
from jinja2.exceptions import UndefinedError
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleUndefinedVariable
from ansible.module_utils.six import text_type
from ansible.module_utils._text import to_native
@ -113,21 +114,16 @@ class Conditional:
if isinstance(conditional, bool):
return conditional
if templar.is_template(conditional):
display.warning('when statements should not include jinja2 '
'templating delimiters such as {{ }} or {%% %%}. '
'Found: %s' % conditional)
# pull the "bare" var out, which allows for nested conditionals
# and things like:
# - assert:
# that:
# - item
# with_items:
# - 1 == 1
if C.CONDITINAL_BARE_VARS:
if conditional in all_vars and VALID_VAR_REGEX.match(conditional):
display.deprecated('evaluating %s as a bare variable, this behaviour will go away and you might need to add |bool'
' to the expression in the future. Also see CONDITIONAL_BARE_VARS configuration toggle.' % conditional, "2.12")
conditional = all_vars[conditional]
if templar.is_template(conditional):
display.warning('conditional statements should not include jinja2 '
'templating delimiters such as {{ }} or {%% %%}. '
'Found: %s' % conditional)
# make sure the templar is using the variables specified with this method
templar.set_available_variables(variables=all_vars)
@ -219,5 +215,5 @@ class Conditional:
# as nothing above matched the failed var name, re-raise here to
# trigger the AnsibleUndefinedVariable exception again below
raise
except Exception as new_e:
except Exception:
raise AnsibleUndefinedVariable("error while evaluating conditional (%s): %s" % (original, e))

@ -1 +1,3 @@
shippable/posix/group1
shippable/posix/group2
shippable/posix/group3

@ -0,0 +1,5 @@
# Do not put test specific entries in this inventory file.
# For script based test targets (using runme.sh) put the inventory file in the test's directory instead.
[testgroup]
testhost ansible_connection=local

@ -0,0 +1,551 @@
# (c) 2014, James Cammarata <jcammarata@ansible.com>
# (c) 2019, Ansible Project
- hosts: testhost
gather_facts: false
vars_files:
- vars/main.yml
tasks:
- name: set conditial bare vars status
set_fact:
bare: "{{lookup('config', 'CONDITINAL_BARE_VARS')|bool}}"
- name: test conditional '=='
shell: echo 'testing'
when: 1 == 1
register: result
- name: assert conditional '==' ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional '=='
shell: echo 'testing'
when: 0 == 1
register: result
- name: assert bad conditional '==' did NOT run
assert:
that:
- result is skipped
- name: test conditional '!='
shell: echo 'testing'
when: 0 != 1
register: result
- name: assert conditional '!=' ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional '!='
shell: echo 'testing'
when: 1 != 1
register: result
- name: assert bad conditional '!=' did NOT run
assert:
that:
- result is skipped
- name: test conditional 'in'
shell: echo 'testing'
when: 1 in [1,2,3]
register: result
- name: assert conditional 'in' ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'in'
shell: echo 'testing'
when: 1 in [7,8,9]
register: result
- name: assert bad conditional 'in' did NOT run
assert:
that:
- result is skipped
- name: test conditional 'not in'
shell: echo 'testing'
when: 0 not in [1,2,3]
register: result
- name: assert conditional 'not in' ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'not in'
shell: echo 'testing'
when: 1 not in [1,2,3]
register: result
- name: assert bad conditional 'not in' did NOT run
assert:
that:
- result is skipped
- name: test conditional 'is defined'
shell: echo 'testing'
when: test_bare is defined
register: result
- name: assert conditional 'is defined' ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'is defined'
shell: echo 'testing'
when: foo_asdf_xyz is defined
register: result
- name: assert bad conditional 'is defined' did NOT run
assert:
that:
- result is skipped
- name: test conditional 'is not defined'
shell: echo 'testing'
when: foo_asdf_xyz is not defined
register: result
- name: assert conditional 'is not defined' ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'is not defined'
shell: echo 'testing'
when: test_bare is not defined
register: result
- name: assert bad conditional 'is not defined' did NOT run
assert:
that:
- result is skipped
- name: test bad conditional 'is undefined'
shell: echo 'testing'
when: test_bare is undefined
register: result
- name: assert bad conditional 'is undefined' did NOT run
assert:
that:
- result is skipped
- name: test bare conditional
shell: echo 'testing'
when: test_bare
register: result
- name: assert bare conditional ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test conditional using a variable
shell: echo 'testing'
when: test_bare_var == 123
register: result
- name: assert conditional using a variable ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test good conditional based on nested variables
shell: echo 'testing'
when: test_bare_nested_good
register: result
- name: assert good conditional based on nested var ran
assert:
that:
- result is changed
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional based on nested variables
shell: echo 'testing'
when: test_bare_nested_bad
register: result
- debug: var={{item}}
loop:
- bare
- result
- test_bare_nested_bad
- name: assert that the bad nested conditional is skipped since 'bare' since 'string' template is resolved to 'false'
assert:
that:
- result is skipped
when: bare|bool
- name: assert that the bad nested conditional did run since non bare 'string' is untempalted but 'trueish'
assert:
that:
- result is skipped
when: not bare|bool
- result is changed
- name: test bad conditional based on nested variables with bool filter
shell: echo 'testing'
when: test_bare_nested_bad|bool
register: result
- name: assert that the bad nested conditional did NOT run as bool forces evaluation
assert:
that:
- result is skipped
#-----------------------------------------------------------------------
# proper booleanification tests (issue #8629)
- name: set fact to string 'false'
set_fact: bool_test1=false
- name: set fact to string 'False'
set_fact: bool_test2=False
- name: set fact to a proper boolean using complex args
set_fact:
bool_test3: false
- name: "test boolean value 'false' string using 'when: var'"
command: echo 'hi'
when: bool_test1
register: result
- name: assert that the task did not run for 'false'
assert:
that:
- result is skipped
- name: "test boolean value 'false' string using 'when: not var'"
command: echo 'hi'
when: not bool_test1
register: result
- name: assert that the task DID run for not 'false'
assert:
that:
- result is changed
- name: "test boolean value of 'False' string using 'when: var'"
command: echo 'hi'
when: bool_test2
register: result
- name: assert that the task did not run for 'False'
assert:
that:
- result is skipped
- name: "test boolean value 'False' string using 'when: not var'"
command: echo 'hi'
when: not bool_test2
register: result
- name: assert that the task DID run for not 'False'
assert:
that:
- result is changed
- name: "test proper boolean value of complex arg using 'when: var'"
command: echo 'hi'
when: bool_test3
register: result
- name: assert that the task did not run for proper boolean false
assert:
that:
- result is skipped
- name: "test proper boolean value of complex arg using 'when: not var'"
command: echo 'hi'
when: not bool_test3
register: result
- name: assert that the task DID run for not false
assert:
that:
- result is changed
- set_fact: skipped_bad_attribute=True
- block:
- name: test a with_items loop using a variable with a missing attribute
debug: var=item
with_items: "{{cond_bad_attribute.results | default('')}}"
register: result
- set_fact: skipped_bad_attribute=False
- name: assert the task was skipped
assert:
that:
- skipped_bad_attribute
when: cond_bad_attribute is defined and 'results' in cond_bad_attribute
- name: test a with_items loop skipping a single item
debug: var=item
with_items: "{{cond_list_of_items.results}}"
when: item != 'b'
register: result
- debug: var=result
- name: assert only a single item was skipped
assert:
that:
- result.results|length == 3
- result.results[1].skipped
- name: test complex templated condition
debug: msg="it works"
when: vars_file_var in things1|union([vars_file_var])
- name: test dict with invalid key is undefined
vars:
mydict:
a: foo
b: bar
debug: var=mydict['c']
register: result
when: mydict['c'] is undefined
- name: assert the task did not fail
assert:
that:
- result is success
- name: test dict with invalid key does not run with conditional is defined
vars:
mydict:
a: foo
b: bar
debug: var=mydict['c']
when: mydict['c'] is defined
register: result
- name: assert the task was skipped
assert:
that:
- result is skipped
- name: test list with invalid element does not run with conditional is defined
vars:
mylist: []
debug: var=mylist[0]
when: mylist[0] is defined
register: result
- name: assert the task was skipped
assert:
that:
- result is skipped
- name: test list with invalid element is undefined
vars:
mylist: []
debug: var=mylist[0]
when: mylist[0] is undefined
register: result
- name: assert the task did not fail
assert:
that:
- result is success
- name: Deal with multivar equality
tags: ['leveldiff']
when: not bare|bool
vars:
toplevel_hash:
hash_var_one: justastring
hash_var_two: something.with.dots
hash_var_three: something:with:colons
hash_var_four: something/with/slashes
hash_var_five: something with spaces
hash_var_six: yes
hash_var_seven: no
toplevel_var_one: justastring
toplevel_var_two: something.with.dots
toplevel_var_three: something:with:colons
toplevel_var_four: something/with/slashes
toplevel_var_five: something with spaces
toplevel_var_six: yes
toplevel_var_seven: no
block:
- name: var subkey simple string
debug:
var: toplevel_hash.hash_var_one
register: sub
when: toplevel_hash.hash_var_one
- name: toplevel simple string
debug:
var: toplevel_var_one
when: toplevel_var_one
register: top
ignore_errors: yes
- name: ensure top and multi work same
assert:
that:
- top is not skipped
- sub is not skipped
- top is not failed
- sub is not failed
- name: var subkey string with dots
debug:
var: toplevel_hash.hash_var_two
register: sub
when: toplevel_hash.hash_var_two
- debug:
var: toplevel_var_two
when: toplevel_var_two
register: top
ignore_errors: yes
- name: ensure top and multi work same
assert:
that:
- top is not skipped
- sub is not skipped
- top is not failed
- sub is not failed
- name: var subkey string with dots
debug:
var: toplevel_hash.hash_var_three
register: sub
when: toplevel_hash.hash_var_three
- debug:
var: toplevel_var_three
when: toplevel_var_three
register: top
ignore_errors: yes
- name: ensure top and multi work same
assert:
that:
- top is not skipped
- sub is not skipped
- top is not failed
- sub is not failed
- name: var subkey string with colon
debug:
var: toplevel_hash.hash_var_four
register: sub
when: toplevel_hash.hash_var_four
- debug:
var: toplevel_var_four
when: toplevel_var_four
register: top
ignore_errors: yes
- name: ensure top and multi work same
assert:
that:
- top is not skipped
- sub is not skipped
- top is not failed
- sub is not failed
- name: var subkey string with spaces
debug:
var: toplevel_hash.hash_var_five
register: sub
when: toplevel_hash.hash_var_five
- debug:
var: toplevel_var_five
when: toplevel_var_five
register: top
ignore_errors: yes
- name: ensure top and multi work same
assert:
that:
- top is not skipped
- sub is not skipped
- top is not failed
- sub is not failed
- name: var subkey with 'yes' value
debug:
var: toplevel_hash.hash_var_six
register: sub
when: toplevel_hash.hash_var_six
- debug:
var: toplevel_var_six
register: top
when: toplevel_var_six
- name: ensure top and multi work same
assert:
that:
- top is not skipped
- sub is not skipped
- name: var subkey with 'no' value
debug:
var: toplevel_hash.hash_var_seven
register: sub
when: toplevel_hash.hash_var_seven
- debug:
var: toplevel_var_seven
register: top
when: toplevel_var_seven
- name: ensure top and multi work same
assert:
that:
- top is skipped
- sub is skipped
- name: test that 'comparisson expression' item works with_items
assert:
that:
- item
with_items:
- 1 == 1
- name: test that 'comparisson expression' item works in loop
assert:
that:
- item
loop:
- 1 == 1

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -eux
ANSIBLE_CONDITIONAL_BARE_VARS=1 ansible-playbook -i inventory play.yml "$@"
ANSIBLE_CONDITIONAL_BARE_VARS=0 ansible-playbook -i inventory play.yml "$@"

@ -1,361 +0,0 @@
# test code for conditional statements
# (c) 2014, James Cammarata <jcammarata@ansible.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: test conditional '=='
shell: echo 'testing'
when: 1 == 1
register: result
- name: assert conditional '==' ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional '=='
shell: echo 'testing'
when: 0 == 1
register: result
- name: assert bad conditional '==' did NOT run
assert:
that:
- "result.skipped == true"
- name: test conditional '!='
shell: echo 'testing'
when: 0 != 1
register: result
- name: assert conditional '!=' ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional '!='
shell: echo 'testing'
when: 1 != 1
register: result
- name: assert bad conditional '!=' did NOT run
assert:
that:
- "result.skipped == true"
- name: test conditional 'in'
shell: echo 'testing'
when: 1 in [1,2,3]
register: result
- name: assert conditional 'in' ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'in'
shell: echo 'testing'
when: 1 in [7,8,9]
register: result
- name: assert bad conditional 'in' did NOT run
assert:
that:
- "result.skipped == true"
- name: test conditional 'not in'
shell: echo 'testing'
when: 0 not in [1,2,3]
register: result
- name: assert conditional 'not in' ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'not in'
shell: echo 'testing'
when: 1 not in [1,2,3]
register: result
- name: assert bad conditional 'not in' did NOT run
assert:
that:
- "result.skipped == true"
- name: test conditional 'is defined'
shell: echo 'testing'
when: test_bare is defined
register: result
- name: assert conditional 'is defined' ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'is defined'
shell: echo 'testing'
when: foo_asdf_xyz is defined
register: result
- name: assert bad conditional 'is defined' did NOT run
assert:
that:
- "result.skipped == true"
- name: test conditional 'is not defined'
shell: echo 'testing'
when: foo_asdf_xyz is not defined
register: result
- name: assert conditional 'is not defined' ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional 'is not defined'
shell: echo 'testing'
when: test_bare is not defined
register: result
- name: assert bad conditional 'is not defined' did NOT run
assert:
that:
- "result.skipped == true"
- name: test bad conditional 'is undefined'
shell: echo 'testing'
when: test_bare is undefined
register: result
- name: assert bad conditional 'is undefined' did NOT run
assert:
that:
- "result.skipped == true"
- name: test bare conditional
shell: echo 'testing'
when: test_bare
register: result
- name: assert bare conditional ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test conditional using a variable
shell: echo 'testing'
when: test_bare_var == 123
register: result
- name: assert conditional using a variable ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test good conditional based on nested variables
shell: echo 'testing'
when: test_bare_nested_good
register: result
- name: assert good conditional based on nested var ran
assert:
that:
- "result.changed == true"
- "result.stdout == 'testing'"
- "result.rc == 0"
- name: test bad conditional based on nested variables
shell: echo 'testing'
when: test_bare_nested_bad
register: result
- name: assert that the bad nested conditional did NOT run
assert:
that:
- "result.skipped == true"
#-----------------------------------------------------------------------
# proper booleanification tests (issue #8629)
- name: set fact to string 'false'
set_fact: bool_test1=false
- name: set fact to string 'False'
set_fact: bool_test2=False
- name: set fact to a proper boolean using complex args
set_fact:
bool_test3: false
- name: "test boolean value 'false' string using 'when: var'"
command: echo 'hi'
when: bool_test1
register: result
- name: assert that the task did not run for 'false'
assert:
that:
- "result.skipped == true"
- name: "test boolean value 'false' string using 'when: not var'"
command: echo 'hi'
when: not bool_test1
register: result
- name: assert that the task DID run for not 'false'
assert:
that:
- "result.changed"
- name: "test boolean value of 'False' string using 'when: var'"
command: echo 'hi'
when: bool_test2
register: result
- name: assert that the task did not run for 'False'
assert:
that:
- "result.skipped == true"
- name: "test boolean value 'False' string using 'when: not var'"
command: echo 'hi'
when: not bool_test2
register: result
- name: assert that the task DID run for not 'False'
assert:
that:
- "result.changed"
- name: "test proper boolean value of complex arg using 'when: var'"
command: echo 'hi'
when: bool_test3
register: result
- name: assert that the task did not run for proper boolean false
assert:
that:
- "result.skipped == true"
- name: "test proper boolean value of complex arg using 'when: not var'"
command: echo 'hi'
when: not bool_test3
register: result
- name: assert that the task DID run for not false
assert:
that:
- "result.changed"
- set_fact: skipped_bad_attribute=True
- block:
- name: test a with_items loop using a variable with a missing attribute
debug: var=item
with_items: "{{cond_bad_attribute.results | default('')}}"
register: result
- set_fact: skipped_bad_attribute=False
- name: assert the task was skipped
assert:
that:
- skipped_bad_attribute
when: cond_bad_attribute is defined and 'results' in cond_bad_attribute
- name: test a with_items loop skipping a single item
debug: var=item
with_items: "{{cond_list_of_items.results}}"
when: item != 'b'
register: result
- debug: var=result
- name: assert only a single item was skipped
assert:
that:
- result.results|length == 3
- result.results[1].skipped
- name: test complex templated condition
debug: msg="it works"
when: vars_file_var in things1|union([vars_file_var])
- name: test dict with invalid key is undefined
vars:
mydict:
a: foo
b: bar
debug: var=mydict['c']
register: result
when: mydict['c'] is undefined
- name: assert the task did not fail
assert:
that:
- "result.failed == false"
- name: test dict with invalid key does not run with conditional is defined
vars:
mydict:
a: foo
b: bar
debug: var=mydict['c']
when: mydict['c'] is defined
register: result
- name: assert the task was skipped
assert:
that:
- "result.skipped == true"
- name: test list with invalid element does not run with conditional is defined
vars:
mylist: []
debug: var=mylist[0]
when: mylist[0] is defined
register: result
- name: assert the task was skipped
assert:
that:
- "result.skipped == true"
- name: test list with invalid element is undefined
vars:
mylist: []
debug: var=mylist[0]
when: mylist[0] is undefined
register: result
- name: assert the task did not fail
assert:
that:
- "result.failed == false"
Loading…
Cancel
Save